이번 시간에는 지난 시간에 이어 NVIDIA Jetson Orin Nano Developer Kit를 해부(4번째 시간)해 보고, 느낀 점을 독자 여러분과 공유해 보고자 한다. 😎
The 4th time 🚀🚀🚀🚀
목차
8. I2C Device Tree and Device Drivers
9. SPI Device Tree and Device Drivers
10. Jetson CSI Camera
11. Jetson AI - LLM 돌려 보기
References
Jetson Orin Nano Dev Kit은 다양한 주변 장치를 제공하고 있다. 따라서 이번 posting을 통해 어떠한 주변 장치가 있는지, 그리고 해당 주변 장치를 어떻게 제어할 수 있는지 최대한 확인해 보도록 하자.
8. I2C Device Tree and Device Drivers
이번 장에서는 I2C 장치를 장착하고, 이를 인식하는 과정을 소개해 보고자 한다. 사실 이와 관련해서는 이미 다른 board(STMP32MP157C DK2, OrangePi R1 Plus, BeaglePlay)를 이용하여 여러 차례 소개한 바 있다.
그럼에도 불구하고, 여기에서 다시 i2c를 언급하는 이유는 단 하나, Jetson Dev Kit에서는 어떻게 처리하는지(뭐가 다른지)를 따져 보기 위해서이다. 😋
[출처 - https://www.totalphase.com/blog/2021/12/i2c-vs-spi-vs-uart-introduction-and-comparison-similarities-differences/]
8.1) HTU21D 온습도 센서와 J12 40 pin 확장 헤더
시험에 사용할 i2c device는 이번에도 역시, HTU21D 온/습도 센서다(있는 것을 재 활용하자 😂). 이미 앞선 posting에서 여러 차례 소개한 만큼, 반복되는 설명은 생략하기로 한다.
[그림 8.1] HTU21D 온/습도 센서
먼저 HTU21D와 연결할 J12 40 pin 확장 헤더를 다시 소환(?)해 보기로 하자. J12 헤더에는 I2C0(SDA, SCL - pin 27, 28)와 I2C1(SDA, SCL - pin 3, 5) 두 그룹의 i2c pin이 나와 있다. 이 중 하나에 HTU21D i2c device를 연결할 수가 있는데, 과연 어느 것이 적당한 지 따져 보도록 하자.
다음으로 아래 table은 Orin SoC TRM(Technical Reference Manual) 문서에서 발췌한 것인데, Orin SoC에는 총 9개의 i2c controller가 있음을 보여준다. 한가지 주의해서 봐야할 점은 아래 테이블에 표시된 I2C instance 번호가 J12 확장 header에 표시된 I2CX와는 다른 의미라는 점이다. J12확장 header에 표시된 I2CX는 사용자 편의를 위해 재정의된 번호이며, 아래 table에서의 I2C Instance 번호는 SoC 내에서의 실제 i2c controller의 번호를 의미한다고 보아야 할 것이다(이 부분이 약간 헷갈릴 수 있다). 😂
[그림 8.3] I2C controller map [출처 - 참고문헌 8]
📌 나중에 보면 알겠지만, 위의 i2c intstance - 1 값(0부터 시작하므로)은 linux kernel에서 i2c controller or bus number에 해당한다.
한편, 아래 그림은 (J12 확장헤더로 표출될) I2C0와 I2C1 port를 Orin SoC pin 기준으로 표현한 블럭도인데, (J12 기준) I2C1 SDA와 SCL은 각각 GP16_I2C8_DAT, GP15_I2C8_CLK SOM pin에 연결되어 있고, I2C0 SDA와 SCL은 각각 GP14_I2C2_DAT, GP13_I2C2_CLK SOM pin에 연결되어 있음을 알 수 있다.
[그림 8.4] Jetson I2C Connections [출처 - 참고문헌 6]
다음으로 참조할 파일은 Jetson Orin NX & Nano Pin 테이블 문서(엑셀 문서)인데, 이 문서에는 I2C0 controller와 I2C1 controller에 대해, Orin SoC pin name과 Verilog pin name 기준으로 잘 정리가 되어 있다.
[그림 8.5] Jetson Orin NX & Nano Pin 테이블 [출처 - 참고문헌 11]
따라서 위의 2개의 그림을 통해 아래와 같은 내용을 정리할 수 있다.
<J12 확장 헤더 - I2C0 pin>
Orin pin name: GP13_I2C2_CLK, GP14_I2C2_DAT
Verilog pin name: GEN2_I2C_SCL, GEN2_I2C2_SDA
<J12 확장 헤더 - I2C1 pin>
Orin pin name: GP15_I2C8_CLK, GP16_I2C8_DAT
Verilog pin name: GEN8_I2C_SCL, GEN8_I2C_SDA
그런데, verilog pin name인 GEN2_I2C와 GEN8_I2C를 device tree에서 찾아보면 어떨까 ?
아래 내용이 tegra234.dtsi에 정의되어 있는 여러 개의 i2c controller 중, gen2_i2c와 gen8_i2c 부분에 해당한다. 이 내용 중에서 각각의 i2c controller에 할당된 주소 부분 즉, i2c@c240000과 i2c@c250000을 잘 기억해 두도록 하자. 💢
지금까지의 내용을 하나로 요약해 보면, 아래와 같은 matching 관계가 나온다.
SoC I2C2 -> GEN2_I2C SCL/SDA (@c240000) -> GP13_I2C2_CLK/GP14_I2C_DAT -> J12 I2C0(J12 확장 헤더 27, 28 pin)
SoC I2C8 -> GEN8_I2C SCL/SDA (@c250000) -> GP15_I2C2_CLK/GP16_I2C_DAT -> J12 I2C1(J12 확장 헤더 3, 5 pin)
📌 다시 말하지만 왼쪽과 오른쪽에 표시된 I2CX는 서로 다른 것이다.
이제 HTU21D i2c device를 target board에 연결할 차례가 되었다. 앞서 설명한 바와 같이 HTU21D를 (J12 헤더의) I2C0와 I2C1, 2개의 pin 모두에 연결하는 것이 가능하나, 시행 착오 끝에 아래와 같이 I2C1에 최종 연결하기로 결정하였다.
<J12 40 pin 확장 헤더> <HTU21D>
+3.3V(1 pin) ===========<보라색>========== +3.3V
GND(9 pin) ===========<회색>=========== GND2
I2C1_SDA(3 pin) ============<흰색>========== SDA
I2C1_SCL(5 pin) ===========<파란색>========== SCL
📌 I2C0에 연결할 수 없는 이유는 나중에 다시 설명하기로 한다....................... (A)
📌 적당한 cable이 없어서, (일반적인 기준과는 다르게) 최대한 가지고 있는 선을 이용해 연결해 보았다. 😂
[그림 8.8] J12 확장 헤더 I2C1 pin에 HTU21D i2c device를 연결한 모습
8.2) HTU21D device tree & device driver 구동
자, 지금까지 크게 어려운 내용없이, i2c pin 연결을 마쳤다. 지금부터는, HTU21D i2c slave device를 제대로 인식시키기 위해 필요한 S/W 작업에 관하여 소개해 보기로 한다.
<HTU21D i2c slave device를 인식시키기 위한 S/W 절차>
1) (J12 확장 헤더) i2c1 controller에 대한 pinmux 설정 추가
-> device tree overlay 설정
2) gen8_i2c(= i2c@c250000) controller 아래에 HTU21D i2c slave device 추가
htu21: htu21@40 {
compatible = "meas,htu21";
reg = <0x40>;
};
3) device driver 구동
_________________________________
$ dtc -O dtb -o pin7_as_gpio.dtbo pin7_as_gpio.dts
-> 편의상, 이전 test 시에 사용했던 pin7_as_gpio.dts 파일 안에, i2c1 controller를 위한 pinmux 설정 내용을 추가하였다.
$ scp ./pin7_as_gpio.dtbo chyi@192.168.55.1:~/workspace
-> 이후 /boot 디렉토리로 복사하자.
-> 이후의 내용은 7장 내용을 함께 참조하도록 하자.
다음으로, htu21d i2c slave device(device tree)는 아래와 같이 추가해 주도록 하자.
[그림 8.10] htu21@40 i2c1 slave device tree 추가 모습
(nv-platform/tegra234-p3768-0000+p3767-xxxx-nv-common.dtsi)
이후 수정된 device tree를 재 compile하고, 결과 파일(dtb)을 target board에 복사하도록 한다.
$ export ARCH=arm64
$ export CROSS_COMPILE=/mnt/hdd/workspace/nvidia/jetson/toolchain/aarch64--glibc--stable-2022.08-1/bin/aarch64-buildroot-linux-gnu-
$ export INSTALL_MOD_PATH=/mnt/hdd/workspace/nvidia/jetson/06222025/Manual/Linux_for_Tegra/rootfs
$ export KERNEL_HEADERS=/mnt/hdd/workspace/nvidia/jetson/06222025/Manual/Linux_for_Tegra/source/kernel/kernel-jammy-src
-> kernel build에 필요한 몇가지 환경 설정을 한다(자신의 환경에 맞게 적절히 조절한다).
$ cd Linux_for_Tegra/source/
$ make dtbs
$ cp kernel-devicetree/generic-dts/dtbs/* ../kernel/dtb/
$ cd ../kernel/dtb
$ scp ./tegra234-p3768-0000+p3767-0005-nv-super.dtb chyi@192.168.55.1:~/workspace
-> 이후 이 파일을 /boot/dtb 아래로 복사하자. 단, 이름은 kernel_tegra234-p3768-0000+p3767-0005-nv-super.dtb로 변경해 주자.
이후, Target board를 재 부팅하여, 변경된 dtb 내용이 제대로 반영되는지 확인하도록 한다(편의상 이 과정은 생략 😋).
마지막으로, HTU21 device driver를 구동시켜 보도록 하자.
<device driver code>
kernel-jammy-src/drivers/iio/humidity/htu21.c
kernel-jammy-src/drivers/iio/common/ms_sensors/ms_sensors_i2c.c
$ make -C kernel/kernel-jammy-src/ menuconfig
Device Drivers --->
Industrial I/O support ---->
Humidity sensors
[그림 8.11] menuconfig 실행 모습
$ cp .config arch/arm64/configs/defconfig
-> htu21 sensor enable 정보를 저장하자.
이후 kernel을 재 빌드한 후, 결과물 즉, Image 파일을 target board에 복사하도록 하자.
$ cd Linux_for_Tegra/source
$ make -C kernel
$ sudo -E make install -C kernel
$ cp kernel/kernel-jammy-src/arch/arm64/boot/Image ../kernel/Image
$ cd ../kernel
$ scp ./Image chyi@192.168.55.1:~/workspace
-> 이후 Image -> /boot 디렉토리로 복사한다. 복사할 때는 Image.backup 형태로 복사하자.
-> 나머지 절차는 동일하므로 생략한다.
자, 모든 준비 절차가 끝났다. 이제 target board를 재부팅하여, s/w 동작을 확인하는 일만 남았다.
8.3) HTU21D 온습도 센서 동작 확인
$ cd /sys/bus/i2c/devices
-> 이 디렉토리로 이동하여 0x40(htu21의 i2c slave address) 주소 관련 내용을 확인해 보자.
chyi@jetsonkit:/sys/bus/i2c/devices$ ls -latotal 0
drwxr-xr-x 2 root root 0 11월 22 2023 .
drwxr-xr-x 4 root root 0 11월 22 2023 ..
lrwxrwxrwx 1 root root 0 11월 22 2023 0-0050 -> ../../../devices/platform/bus@0/3160000.i2c/i2c-0/0-0050
lrwxrwxrwx 1 root root 0 11월 22 2023 0-0057 -> ../../../devices/platform/bus@0/3160000.i2c/i2c-0/0-0057
lrwxrwxrwx 1 root root 0 11월 22 2023 1-0025 -> ../../../devices/platform/bus@0/c240000.i2c/i2c-1/1-0025
lrwxrwxrwx 1 root root 0 11월 22 2023 1-0040 -> ../../../devices/platform/bus@0/c240000.i2c/i2c-1/1-0040 //어라, i2c1 controller(j12 pin 기준으로는 i2C0 controller)에는 htu21d와 동일한 i2c 주소인 0x40을 사용하는 녀석이 이미 붙어 있다. 때문에 여기에 htu21d를 붙일 수가 없었다. .................... (A)
lrwxrwxrwx 1 root root 0 11월 22 2023 4-003c -> ../../../devices/platform/bpmp/bpmp:i2c/i2c-4/4-003c
lrwxrwxrwx 1 root root 0 11월 22 2023 7-0040 -> ../../../devices/platform/bus@0/c250000.i2c/i2c-7/7-0040 //이 내용이 HTU21D 센서 관련 내용이다. SoC 기준으로 i2c7 controller, J12 pin 기준으로는 I2C1 controller에 붙어 있다.
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-0 -> ../../../devices/platform/bus@0/3160000.i2c/i2c-0
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-1 -> ../../../devices/platform/bus@0/c240000.i2c/i2c-1
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-2 -> ../../../devices/platform/bus@0/3180000.i2c/i2c-2
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-4 -> ../../../devices/platform/bpmp/bpmp:i2c/i2c-4
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-5 -> ../../../devices/platform/bus@0/31b0000.i2c/i2c-5
lrwxrwxrwx 1 root root 0 11월 22 2023 i2c-7 -> ../../../devices/platform/bus@0/c250000.i2c/i2c-7
lrwxrwxrwx 1 root root 0 6월 26 21:45 i2c-9 -> ../../../devices/platform/13800000.display/i2c-9
이어서, 7-0040 디렉토리로 이동하여, 온/습도 값을 읽어 보자.
chyi@jetsonkit:/sys/bus/i2c/devices$ cd 7-0040
chyi@jetsonkit:/sys/bus/i2c/devices/7-0040$ ls -la
total 0
drwxr-xr-x 4 root root 0 11월 22 2023 .
drwxr-xr-x 5 root root 0 11월 22 2023 ..
lrwxrwxrwx 1 root root 0 11월 22 2023 driver -> ../../../../../../bus/i2c/drivers/htu21
drwxr-xr-x 3 root root 0 11월 22 2023 iio:device0
-r--r--r-- 1 root root 4096 11월 22 2023 modalias
-r--r--r-- 1 root root 4096 11월 22 2023 name
lrwxrwxrwx 1 root root 0 6월 26 21:46 of_node -> ../../../../../../firmware/devicetree/base/bus@0/i2c@c250000/htu21@40
drwxr-xr-x 2 root root 0 6월 26 21:46 power
lrwxrwxrwx 1 root root 0 11월 22 2023 subsystem -> ../../../../../../bus/i2c
-rw-r--r-- 1 root root 4096 11월 22 2023 uevent
chyi@jetsonkit:/sys/bus/i2c/devices/7-0040$ cd iio\:device0/
chyi@jetsonkit:/sys/bus/i2c/devices/7-0040/iio:device0$ ls -al
total 0
drwxr-xr-x 3 root root 0 11월 22 2023 .
drwxr-xr-x 4 root root 0 11월 22 2023 ..
-r--r--r-- 1 root root 4096 6월 26 21:46 battery_low
-rw-r--r-- 1 root root 4096 6월 26 21:46 heater_enable
-rw-r--r-- 1 root root 4096 6월 26 21:46 in_humidityrelative_input
-rw-r--r-- 1 root root 4096 6월 26 21:46 in_temp_input
-r--r--r-- 1 root root 4096 6월 26 21:45 name
lrwxrwxrwx 1 root root 0 6월 26 21:46 of_node -> ../../../../../../../firmware/devicetree/base/bus@0/i2c@c250000/htu21@40
drwxr-xr-x 2 root root 0 6월 26 21:46 power
-rw-r--r-- 1 root root 4096 6월 26 21:46 sampling_frequency
-r--r--r-- 1 root root 4096 6월 26 21:46 sampling_frequency_available
lrwxrwxrwx 1 root root 0 11월 22 2023 subsystem -> ../../../../../../../bus/iio
-rw-r--r-- 1 root root 4096 11월 22 2023 uevent
chyi@jetsonkit:/sys/bus/i2c/devices/7-0040/iio:device0$ cat in_temp_input
28837
chyi@jetsonkit:/sys/bus/i2c/devices/7-0040/iio:device0$ cat in_humidityrelative_input
52270
OK, 온도와 습도 값이 제대로 출력된다. 현재 온도는 28.837도(섭씨), 습도는 52.270%로 높은 편이다(요즘 비가와서 좀 습한 편이다).
우리는 앞에서 이미, 아래와 같은 pin matching 관계를 파악했었다.
SoC I2C2 -> GEN2_I2C SCL/SDA (@c240000) -> GP13_I2C2_CLK/GP14_I2C_DAT -> J12 I2C0(J12 확장 헤더 27, 28 pin)
SoC I2C8 -> GEN8_I2C SCL/SDA (@c250000) -> GP15_I2C2_CLK/GP16_I2C_DAT -> J12 I2C1(J12 확장 헤더 3, 5 pin)
Kernel에서는 (위의) 왼쪽 I2C1 ~ I2C8 instance를 내부적으로 i2c-0 ~ i2c-7 형태(bus)로 표현하므로, HTU21D 온/습도 센서 상태를 확인하기 위해서는, i2cdetect 명령 파라미터인 I2CBUS 필드에 숫자 7을 넣어주면 된다.
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
참고로, I2CBUS 값으로 1을 지정할 경우도 테스트해 보았는데, (앞서도 언급했듯이) 공교롭게도 i2c address 0x40을 사용하는 녀석이 보인다.
$ i2cdetect -y -r 1
이와 관련하여, nv-platform/tegra234-p3768-0000+p3767-xxxx-nv-common.dtsi 파일을 들여다 보니, i2c@c240000 controller에 ina3221@40이라는 i2c device가 붙어 있는 것이 보인다.
이상으로 HTU21D i2c device를 Jetson Orin Nano Dev Kit에 붙여 보고, 정상적으로 동작시키기 위한 전 과정을 확인해 보았다.
9. SPI Device Tree and Device Drivers
이번 장에서는 Jetson Dev Kit에 SPI 장치를 하나 장착하고, 이를 인식시키는 과정을 소개해 보고자 한다. 이미 앞 장에서 i2c에 관하여 파악해 보았으니, spi도 비슷한 순서를 따라서 내용 파악에 바로 돌입해 보도록 하자.
[출처 - https://www.totalphase.com/blog/2021/12/i2c-vs-spi-vs-uart-introduction-and-comparison-similarities-differences/]
9.1) ADXL345 3축 가속도 센서와 J12 40 pin 확장 헤더
시험에 사용할 spi device는 역시 전과 동일하게 Analog Devices 사에서 만든 ADXL345 3축 가속도 센서이다(역시 이번에도 재활용이다. 😁).
[그림 9.1] ADXL345 spi device(1)
[출처 - https://electronicsprojects.in/components/adxl345-accelerometer-sensor-pinout-and-projects]
📌 SDO는 MISO(Master In Slave Out) 즉, slave device -> CPU로 데이터 송신하는 pin에 해당하고, SDA는 MOSI(Master Out Slave In) 즉 CPU -> slave device로 (slave device 입장에서) 데이터 수신 pin에 해당한다.
아래 몇개의 그림은 ADXL345 datasheet에서 발췌한 것으로, ADXL345와 CPU(processor)간의 연결 방식을 보여준다.
[그림 9.3] ADXL345 3축 가속도 센서 application block도 [출처: 참고문헌 27]
📌 ADXL345는 재밌게도 interrupt를 발생시킬 수 있다.
[그림 9.4] ADXL345 SPI 연결도 - 3 wire [출처: 참고문헌 27]
[그림 9.5] ADXL345 SPI 연결도 - 4 wire [출처: 참고문헌 27]
📌 이번 posting에서는 이 방식을 사용해야 한다.
📌 ADXL345는 SPI 말고도 I2C로도 연결 가능하다.
(i2c의 경우와 마찬가지로) 먼저 ADXL345와 연결할 J12 40 pin 확장 헤더를 다시 들여다 보기로 하자. J12 헤더에는 SPI0(CS, SCK, MOSI, MISO - pin 24, 23, 19, 21)와 SPI1(CS, SCK, MOSI, MISO - pin 18, 13, 37, 22) 두 그룹의 spi pin이 보인다. 이 중 하나에 ADXL345 spi device를 연결할 수가 있는데, 과연 어느 것이 적당한 지 따져 보도록 하자.
📌 특이하게도 CS(Chip Select) pin이 SPI0_CS0/CS1, SPI1_CS0/CS1으로 각각 2개씩 나와 있다.
한편, 아래 그림은 (J12 확장헤더로 표출될) SPI0와 SPI1 port를 SoC pin 기준으로 표현한 블럭도이다.
다음으로 아래 엑셀 문서를 통해 Orin(SoC) pin name와 Verilog pin name 기준으로 정리된 내용을 확인해 볼 수가 있다.
[그림 9.9] Jetson Orin NX & Nano Pin 테이블 [출처 - 참고문헌 11]
따라서 여기까지의 내용을 정리해 보면 다음과 같다.
<J12 확장 헤더 - SPI0 pin>
Orin(SoC) pin name:
GP47_SPI1_SCK
GP48_SPI1_MISO
GP49_SPI1_MOSI
GP50_SPI1_CS0
Verilog pin name:
SPI1_SCK
SPI1_MISO
SPI1_MOSI
SPI1_CS0
<J12 확장 헤더 - SPI1 pin>
Orin(SoC) pin name:
GP36_SPI3_SCK
GP37_SPI3_MISO
GP38_SPI3_MOSI
GP39_SPI3_CS0
Verilog pin name:
SPI3_SCK
SPI3_MISO
SPI3_MOSI
SPI3_CS0
그렇다면, device tree에서는 J12 확장 헤더 중 SPI pin 부분을 어떻게 표현하고 있을까 ? 먼저, J12 확장 헤더 기준 SPI0 pin에 대한 device tree node 정보는 다음과 같다.
Pin 19(MOSI), Pin 21(MISO), Pin 23(CLK), Pin 24(CS)
[그림 9.10] J12 확장 헤더 SPI0(SoC 기준으로는 SPI1)
(nv-platform/tegra234-p3768-0000+p3767-xxxx-nv-common.dtsi)
📌 역시 여기에서도 헷갈리면 안되는 부분이, J12 확장 헤더 기준으로 표현된 SPI0, SPI1과 SoC 기준의 그것은 서로 별개라는 점이다. 위의 device tree에서 SPI1이라고 표시한 부분(주석 내용)은 J12기준이 아니라 SoC 기준이다.
다음 내용은 J12 SPI1 pin에 대한 것이다.
Pin 37(MOSI), Pin 22(MISO), Pin 13(CLK), Pin 18(CS)
따라서, 지금까지의 내용을 하나로 종합해 보면, 아래와 같은 matching 관계를 얻을 수 있다.
SPI1(Soc) ->
SPI1_MOSI, SPI1_MISO, SPI1_SCK, SPI1_CS0 ->
GP49_SPI1_MOSI, GP48_SPI1_MISO, GP47_SPI1_SCK, GP50_SPI1_CS0 ->
J12 Pin 19(MOSI), Pin 21(MISO), Pin 23(CLK), Pin 24(CS)
SPI3(SoC) ->
SPI3_MOSI, SPI3_MISO, SPI3_SCK, SPI3_CS0 ->
GP38_SPI3_MOSI, GP37_SPI3_MISO, GP36_SPI3_SCK, GP39_SPI3_CS0 ->
J12 Pin 37(MOSI), Pin 22(MISO), Pin 13(CLK), Pin 18(CS)
자, 지금까지 SPI h/w 구성에 관한 내용 파악이 끝났으니, 이제 ADXL345 spi device를 target board에 연결할 차례이다. 앞서 설명한 바와 같이 SPI0와 SPI1 2개의 pin 모두에 연결이 가능하나, 여기에서는 우선 SPI0에 연결해 보도록 하자.
[그림 9.12] SPI Master(Raspberry Pi) & Slave(ADXL345) 연결 예 [출처 - 참고문헌 30]
📌 실제로는 Raspeberry Pi를 위한 그림인데, Jetson Kit에서도 pin number가 동일하다.<Tegra234 SoC> <J12 확장 헤더> <ADXL345>
+3.3V ============ Pin 1(+3.3V) =========== VCC
GND ============ Pin 9(GND) =========== GND
SPI1_CSO ============ Pin 24(CS) =========== CS
SPI1_SCK ============ Pin 23(CLK) =========== SCL
SPI1_MISO ============ Pin 21(MISO) =========== SDO
SP1_MOSI ============ Pin 19(MOSI) =========== SDA
[그림 9.13] J12 확장 헤더 SPI0 pin에 ADXL345 spi device를 연결한 모습
9.2) ADXL345 device tree & device driver 구동
지금부터는 ADXL345 spi slave device를 제대로 인식시키기 위해 필요한 S/W 작업에 관하여 소개해 보기로 한다.
<ADXL345 spi slave device를 인식시키기 위한 S/W 절차>
1) (J12 확장 헤더) spi0 controller에 대한 pinmux 설정 추가
-> device tree overlay 설정
2) SoC 기준 SPI1 controller(spi@3210000) 아래에 ADXL345 spi slave device 추가
adxl345: adxl345@0 {
compatible = "adi,adxl345";
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
reg = <0>;
compatible = "adi,adxl345";
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
reg = <0>;
//이번 posting에서는 interrupt 설정은 뺄 것이다.
//interrupt-parent = <&gpio1>;
//interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
};3) device driver 구동
_________________________________
이후 수정된 device tree를 재 compile하고, 결과 파일(dtb)을 target board에 복사하도록 한다.
$ export ARCH=arm64
$ export CROSS_COMPILE=/mnt/hdd/workspace/nvidia/jetson/toolchain/aarch64--glibc--stable-2022.08-1/bin/aarch64-buildroot-linux-gnu-
$ export INSTALL_MOD_PATH=/mnt/hdd/workspace/nvidia/jetson/06222025/Manual/Linux_for_Tegra/rootfs
$ export KERNEL_HEADERS=/mnt/hdd/workspace/nvidia/jetson/06222025/Manual/Linux_for_Tegra/source/kernel/kernel-jammy-src
-> kernel build에 필요한 몇가지 환경 설정을 한다(자신의 환경에 맞게 적절히 조절한다).
$ cd Linux_for_Tegra/source/
$ make dtbs
$ cp kernel-devicetree/generic-dts/dtbs/* ../kernel/dtb/
$ cd ../kernel/dtb
$ scp ./tegra234-p3768-0000+p3767-0005-nv-super.dtb chyi@192.168.55.1:~/workspace
-> 이후 이 파일을 /boot/dtb 아래로 복사하자. 단, 이름은 (기존에 사용하던) kernel_tegra234-p3768-0000+p3767-0005-nv-super.dtb로 변경해 주자.
부팅 후, 아래 위치에서 spi device 상태를 확인해 보도록 한다.
chyi@jetsonkit:~$ cd /sys/bus/spi/devices/
chyi@jetsonkit2:/sys/bus/spi/devices$ ls -la
chyi@jetsonkit2:/sys/bus/spi/devices$ ls -la
total 0
drwxr-xr-x 2 root root 0 11월 22 2023 .
drwxr-xr-x 4 root root 0 11월 22 2023 ..
lrwxrwxrwx 1 root root 0 11월 22 2023 spi0.0 -> ../../../devices/platform/bus@0/3210000.spi/spi_master/spi0/spi0.0
lrwxrwxrwx 1 root root 0 6월 30 15:15 spi1.0 -> ../../../devices/platform/bus@0/3230000.spi/spi_master/spi1/spi1.0
lrwxrwxrwx 1 root root 0 6월 30 15:15 spi1.1 -> ../../../devices/platform/bus@0/3230000.spi/spi_master/spi1/spi1.1
drwxr-xr-x 2 root root 0 11월 22 2023 .
drwxr-xr-x 4 root root 0 11월 22 2023 ..
lrwxrwxrwx 1 root root 0 11월 22 2023 spi0.0 -> ../../../devices/platform/bus@0/3210000.spi/spi_master/spi0/spi0.0
lrwxrwxrwx 1 root root 0 6월 30 15:15 spi1.0 -> ../../../devices/platform/bus@0/3230000.spi/spi_master/spi1/spi1.0
lrwxrwxrwx 1 root root 0 6월 30 15:15 spi1.1 -> ../../../devices/platform/bus@0/3230000.spi/spi_master/spi1/spi1.1
chyi@jetsonkit:/sys/bus/spi/devices$ cd spi0.0
chyi@jetsonkit:/sys/bus/spi/devices/spi0.0$ ls -la
total 0
drwxr-xr-x 4 root root 0 11월 22 2023 .
drwxr-xr-x 6 root root 0 11월 22 2023 ..
-rw-r--r-- 1 root root 4096 6월 29 15:44 driver_override
-r--r--r-- 1 root root 4096 6월 29 15:44 modalias
lrwxrwxrwx 1 root root 0 6월 29 15:44 of_node -> ../../../../../../../firmware/devicetree/base/bus@0/spi@3210000/adxl345@0
drwxr-xr-x 2 root root 0 6월 29 15:44 power
drwxr-xr-x 2 root root 0 6월 29 15:44 statistics
lrwxrwxrwx 1 root root 0 6월 29 15:42 subsystem -> ../../../../../../../bus/spi
-rw-r--r-- 1 root root 4096 11월 22 2023 uevent
-r--r--r-- 1 root root 4096 6월 29 15:44 waiting_for_supplier
chyi@jetsonkit:/sys/bus/spi/devices/spi0.0$ ls -la
total 0
drwxr-xr-x 4 root root 0 11월 22 2023 .
drwxr-xr-x 6 root root 0 11월 22 2023 ..
-rw-r--r-- 1 root root 4096 6월 29 15:44 driver_override
-r--r--r-- 1 root root 4096 6월 29 15:44 modalias
lrwxrwxrwx 1 root root 0 6월 29 15:44 of_node -> ../../../../../../../firmware/devicetree/base/bus@0/spi@3210000/adxl345@0
drwxr-xr-x 2 root root 0 6월 29 15:44 power
drwxr-xr-x 2 root root 0 6월 29 15:44 statistics
lrwxrwxrwx 1 root root 0 6월 29 15:42 subsystem -> ../../../../../../../bus/spi
-rw-r--r-- 1 root root 4096 11월 22 2023 uevent
-r--r--r-- 1 root root 4096 6월 29 15:44 waiting_for_supplier
OK, 일단 spi@32100000 controller에 adxl345@0 device가 연결된 모습이 보인다.
그럼, 마지막으로 adxl345용 device driver를 구동시켜 보자.
$ make -C kernel/kernel-jammy-src/ menuconfig
<device driver code>
drivers/iio/accel/adxl345_spi.c
drivers/iio/accel/adxl345_core.c
Device Drivers --->
Industrial I/O support ---->
Accelerometers ---->
$ cd Linux_for_Tegra/source/kernel/kernel-jammy-src
$ cp .config arch/arm64/configs/defconfig
-> adxl345 sensor enable 정보를 저장하자.
이후 kernel을 재 빌드한 후, 결과물 즉, Image 파일을 target board에 복사하도록 하자.
$ cd Linux_for_Tegra/source
$ make -C kernel
...
CALL scripts/atomic/check-atomics.sh
CALL scripts/checksyscalls.sh
CHK include/generated/compile.h
UPD kernel/config_data
GZIP kernel/config_data.gz
CC kernel/configs.o
CC drivers/iio/accel/adxl345_spi.o
CC drivers/iio/accel/adxl345_core.o
AR drivers/iio/accel/built-in.a
AR drivers/iio/built-in.a
AR kernel/built-in.a
AR drivers/built-in.a
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
AR init/built-in.a
LD vmlinux.o
...
$ sudo -E make install -C kernel
$ cp kernel/kernel-jammy-src/arch/arm64/boot/Image ../kernel/Image
$ cd ../kernel
$ scp ./Image chyi@192.168.55.1:~/workspace
-> 이후 Image -> /boot 디렉토리로 복사한다. 나머지 절차는 동일하므로 생략한다.
자, 모든 준비 절차가 끝났다. 이제 target board를 재부팅하여, s/w 동작을 확인하는 일만 남았다.
<부팅 중>
...
___________________________________________________________
어, 그런데, 부팅 과정에서 adxl345 spi driver가 오동작하는 것 같다.
chyi@jetsonkit2:~$ sudo dmesg | grep adxl345
[ 8.794701] adxl345_spi spi1.1: Invalid device ID: 66, expected e5
[ 8.794701] adxl345_spi spi1.1: Invalid device ID: 66, expected e5
어랍쇼, 왜 이런 문제가 발생할까 ?
pin 연결을 몇차례 확인해 보았지만, 이상이 없다. 그렇다면 ADXL345 chip이 damage라도 입은 걸까 ?
<시도해 본 내용>
1) J12 SPI0 대신 SPI1으로 바꿔 연결하여도 동일한 결과 발생
2) Jumper cable 중 헐거운 녀석을 다른 것으로 교체하여도 동일
3) Chip select pin을 CS0 -> CS1으로 변경해 보았으나, 결과 동일함.
4) Breakout spec 상 VCC가 3V ~ 6V이어서, 3.3V 대신 5V pin에 연결해 보았으나, 결과 동일함.
5) 혹시 몰라, ADXL345 breakout 보드를 다른 것으로 교체 후 시험해 보아도 결과 동일함.
-> 따라서 시험에 사용하던 breakout board가 damage를 입은 것 같지는 않음.
6) jetson-io.py를 이용해 device tree overlay 설정을 재 확인해 보았으나, 특이점 발견 못함.
[그림 9.15] 4pin 헤더 중 현재 enabled되어 있는 pin 확인
[그림 9.16] jetson-io-hdr40-user-custom.dtbo 파일을 fdtdump한 모습
<TBD>
아무래도, Logic Analyzer를 이용하여 MOSI와 MISO pin의 파형을 찍어 봐야겠다. Logic Analyzer가 준비되는 대로 다시 시도해 보기로 한다. 🏄
10. Jetson CSI Camera
이번 장에서는 아주 오랫만에 Camera 관련 이야기를 꺼내 볼까 한다. ⛱
[그림 10.1] Jetson Orin Nano Super Dev Kit with CSI Camera
Camera와 관련해서는 오래 전에 Raspberry Pi & Pi camera를 대상으로 한차례 소개한 바가 있다. 오래 전 내용이기는 하지만, MIPI CSI-2, V4L2 subsystem 등 전반적인 내용을 파악하는데 도움이 될 것으로 보인다. 💪
10.1) Jetson Orin MIPI CSI-2 Interface
먼저 Jetson Orin SoC에는 2-lane MIPI-CSI2 interface 4개와 4-lane MIPI-CSI2 interface 2개가 나와 있다.
[그림 10.2] Jetson Orin CSI 2-Lane Connections [출처 - 참고문헌 6]
📌 D-PHY: clock lane(+/- 2 line으로 구성)과 data lane(2개 or 4개의 data lane)으로 구성되어 있다.[그림 10.3] Jetson Orin CSI 4-Lane Connections [출처 - 참고문헌 6]
📌 CSIN, CSIN+1, CLK lane은 모두 Input signal 즉, Camera -> SoC 방향의 연결인 것을 알 수 있다.
일반적으로 Camera는 I2C를 통해 register 설정을 한다. Jetson Orin의 경우도, 이를 위해 GP54_I2C3_CLK와 GP55_I2C_DAT 2개의 port가 마련되어 있다. 또한, Camera를 제어하기 위한 Clock/Control이라는 pin도 필요하다.
[그림 10.4] Jetson Orin Camera Control Connections [출처 - 참고문헌 6]
(위의 그림이 곧 바로 이해가 되지 않아) Carrier Board 블럭도 중, Camera Interface 부분만을 집중해서 보았더니, CSI Camera로 부터 CSI0, CSI1, CLK1(or CSI2, CSI3, CLK2) 신호가 SoC 쪽으로 들어오며(Video input), 반대로 MCLK/CTRL signal은 SoC에서 Camera로 나가는 것을 알 수가 있다. 또한 Camera 속성 제어를 위해 사용되는 I2C 연결은 양방향(Camera <-> SoC)인데, 중간에 Mux를 사용하여 2개의 Camera를 선택적으로 제어 가능하도록 하고 있다. 💢
10.2) Pi Camera V2.1
이번에 사용할 Camera라는 Sony에서 만든 IMX219 chip을 사용하는 Pi camera V2.1이다.
[그림 10.6] Pi camera V2.1 (15pin cable 사용)
항목 | 상세 내용 |
Still resolution | 8 Megapixels |
Video modes | 1080p30, 720p60, 640 x 480p60/90 |
Sensor 모델 | Sony IMX219 |
Sensor resolution | 3280 x 2464 pixels |
Linux integration | V4L2 driver available |
그런데, Pi camera를 Jetson Kit에 장착하려고 보니, 한가지 문제가 있다. 😭
아래 Carrier board의 J20, J21 Connector를 보니, 모두 22 pin 인터페이스로 되어 있다.
J20 Camera (#0) Connector (22 pos, 0.5 mm pitch)
J21 Camera (#1) Connector (22 pos, 0.5 mm pitch)
[그림 10.7] Jetson Orin Nano Dev Kit Carrier board [출처 - 참고문헌 5]
한편, 기존에 Raspberry Pi에 연결하던 cable의 경우, 15pin ribon cable이기 때문에, 어쩔 수 없이 아래와 같이 15 pin을 22 pin으로 전환시켜주는 cable을 추가로 구매할 수 밖에 없었다. 가격은 얼마 안하긴 한다. 😓
[그림 10.8] Pi Camera 15 pin to 22 pin 변환 케이블(좌측 ribon cable 하단: 22 pin, 상단 15 pin)
📌 재밌는 것은 폭이 좁은 쪽이 22pin이라는 점이다.
[그림 10.9] Jetson Kit에 Pi camera 장착 모습
주문하는 김에 IMX219 8MP(Mega Pixel) 광각 카메라도 1개 추가 주문했다. 📷
[그림 10.10] IMX219 8MP 광각 카메라
📌 넓은 시야을 가진 렌즈를 사용한다는 차이만 있을 뿐, 나머지 h/w 스펙은 앞서 소개한 Pi camera V2.1과 동일하다.
[그림 10.11] Jetson Dev Kit와 2개의 Pi Camera
📌 자리가 왠지 좀 지져분하다. 😓10.3) Jetson Linux Camera Device Tree Overlay 설정
다음으로 할 일은 jetson-io,py를 이용해서 Camera 관련 device tree overlay 설정을 진행하는 것이다.
$ sudo /opt/nvidia/jetson-io/jetson-io.py
[그림 10.12] jetson-io.py를 이용하여 Camera 관련 device tree 설정하기 - Step#1
[그림 10.13] jetson-io.py를 이용하여 Camera 관련 device tree 설정하기 - Step#2
[그림 10.14] jetson-io.py를 이용하여 Camera 관련 device tree 설정하기 - Step#3
이후 target board는 자동 reboot 된다.
부팅 후, 아래 명령을 사용하여 Camera 관련 device tree 설정 내용을 확인해 보도록 한다.
$ fdtdump /boot/tegra234-p3767-camera-p3768-imx219-dual.dtbo > ~/workspace/camera.txt
-> 현재 동작 중인 device tree overlay file을 fdtdump 하여 내용을 들여다 보도록 하자.
$ vi ~/workspace/camera.txt
그런데, 사실 위의 내용은 아래 파일을 기초로 하고 있다.
tegra234-camera-rbpcv2-imx219.dtsi
^
|
tegra234-p3767-camera-p3768-imx219-dual.dts
[그림 10.18] tegra234-p3767-camera-p3768-imx219-dual.dts
📌 이 파일은 Linux_for_Tegra/source/hardware/nvidia/t23x/nv-public/overlay 아래에 있다.
[그림 10.19] tegra234-camera-rbpcv2-imx219.dtsi
10.4) Jetson Linux Camera Architecture와 영상 출력 시험하기
이제 Jetson Camera를 동작시키기 위한 모든 준비가 끝났다. 끝으로 아래 그림을 한번 확인한 후, camera test에 들어가 보도록 하자.
[그림 10.20] Jetson Linux Camera Architecture Stack [출처 - 참고문헌 3]
📌 녹색으로 표시된 block이 NVIDIA에서 제공하는 것들이다. 위 그림을 보면, V4L2, GStreamer 및 libargus 기반의 3개의 application 실행이 가능해 보인다.
- libargus: camera core stack에 기반한 low-level API를 제공함
- nvarguscamerasrc: NVIDIA camera GStreamer plugin
- v4l2src: linux v4l2 application
$ ls -l /dev/video*
crw-rw----+ 1 root video 81, 0 7월 4 17:42 /dev/video0
crw-rw----+ 1 root video 81, 4 7월 4 17:42 /dev/video1
crw-rw----+ 1 root video 81, 4 7월 4 17:42 /dev/video1
$ v4l2-compliance -d /dev/video0
-> device tree overlay 설정 후, 전에 없었던 /dev/video0, 1 파일이 보인다.
(각설하고) 오늘도 JetsonHacks의 Jim 형님의 도움을 받아, Jetson camera를 구동시켜 보자. 👍
$ git clone https://github.com/JetsonHacksNano/CSI-Camera
$ cd CSI-Camera
$ python3 ./simple_camera.py
-> simple camera program을 구동시킨다.
[그림 10.23] simple_camera.py 실행 모습
[그림 10.24] simple_camera.py 실행 모습 - 화면 출력
📌 위의 python code는 내부적으로 아래와 같이 gst-launch-1.0(gstreamer) app을 실행시켜 준다.
gst-launch-1.0 nvarguscamerasrc sensor-id=0 ! 'video/x-raw(memory:NVMM), width=1920, height=1080, framerate=30/1' ! nvvidconv flip-method=0 ! video/x-raw, width=960, height=540, format=BGRx ! videoconvert ! video/x-raw, format=BGR ! appsink
참고로, 아래 명령을 이용해도 camera 영상 출력이 가능하다.
nvgstcapture-1.0 --sensor-id=0
$ python3 ./face_detect.py
-> 다음 program은 얼굴 인식 code이다.
[그림 10.25] face_detect.py 실행 모습 - 스**벅*스 세이렌의 얼굴에 파란색 사각형 표시됨.
마지막으로, dual_camera.py code를 실행해 본다.
$ python3 ./dual_camera.py
-> 2개의 Pi camera를 통해 영상이 들어온다. 😎
[그림 10.26] dual_camera.py 실행 모습
각각의 python code에 대한 분석 및 다양한 application 테스트 등 검증할 내용이 아직 많이 남아 있지만, 이 정도 선에서 1차 마무리하고자 한다. 😂
_____________________________________________________________________
지금까지 4개의 장에 걸쳐서 GPIO, I2C, SPI, MIPI-CSI2 Camera 관련 내용을 간단히 정리해 보았다. Jetson Dev Kit에는 이 밖에도 고민해 보아야 하는 다양한 주제들이 많이 있다.
<TODO>
1) Multimedia, Hardware Acceleration in the WebRTC Framework, Graphics ...
2) PCIe - Orin Module, Wi-Fi/BT module, NVMe module
3) Audio
4) HDMI & DP
5) USB 2.0, 3.2
6) CAN
7) Power sequence
앞으로 기회가 되면 위의 내용을 소개하는 시간을 틈틈히 가져 보도록 하겠다.
늘 느끼는 거지만, 시작할 때 계획과는 다르게 뒤로 갈수록 내용이 점점 부실해지는 경향이 있다. 독자 여러분의 이해를 부탁드리며, 다음 장 부터는 AI 이야기로 넘어가도록 하자. 😎
To be continued...
References
[1] jetson-orin-datasheet-nano-developer-kit-3475392-r2.pdf
-> 제품 brochure
[2] https://www.nvidia.com/en-us/autonomous-machines/embedded-systems/jetson-orin/nano-super-developer-kit/
-> Jetson Orin Nano Super Developer Kit 공식 site
[3] https://docs.nvidia.com/jetson/archives/r36.4.3/DeveloperGuide/
-> NVIDIA Jetson Linux developer guide 문서 *****
[4] https://www.jetson-ai-lab.com/initial_setup_jon.html
-> Initial setup guide for etson Orin Nano Developer Kit *****
[5] Jetson Orin Nano Developer Kit Carrier Board, SP-11324-001_v1.3 pdf
[6] Jetson Orin NX Series and Jetson Orin Nano Series Product Design Guide
[7] Jetson Orin NX Series and Jetson Orin Nano Series Pin and Function Names Guide
[8] NVIDIA Orin Series System-on-Chip Technical Reference Manual
[9] NVIDIA Jetson Orin Nano Series Modules Data Sheet - Ampere GPU + Arm Cortex-A78AE CPU + LPDDR5
-> Jetson Orin Nano Developer Kit 관련 h/w 문서
[10] https://docs.nvidia.com/jetson/archives/r35.4.1/DeveloperGuide/text/AR/BootArchitecture/JetsonOrinSeriesBootFlow.html
-> Jetson Orin Nano Boot Flow
[11] Jetson_Orin_NX_Orin_Nano_Pin_Descriptiosn.xlsx
->Jetson Orin Nano pin description 엑셀 문서
[12] https://jetsonhacks.com/
[13] https://github.com/jetsonhacks
[14] https://jetsonhacks.com/2025/03/13/build-jetson-orin-kernel-and-modules/
[15] https://jetsonhacks.com/2025/04/07/device-tree-overlays-on-jetson-scary-but-fun/
[16] https://jetsonhacks.com/nvidia-jetson-orin-nano-gpio-header-pinout/
[17] https://github.com/JetsonHacksNano/CSI-Camera
[18] https://jetsonhacks.com/2023/04/05/using-the-jetson-orin-nano-with-csi-cameras/
-> Cool site~
[19] https://developer.ridgerun.com/wiki/index.php/NVIDIA_Jetson_-_Device_Tree_Overlay
[20] https://github.com/TechNexion-Vision/TEV-JetsonOrin-Nano_device-tree
[21] https://nvidia-jetson.piveral.com/jetson-orin-nano/configuring-gpio-output-on-nvidia-jetson-orin-nano/
-> gpio configurations
[22] https://bootlin.com/blog/enabling-new-hardware-on-raspberry-pi-with-device-tree-overlays/
[23] https://wikidocs.net/3205
-> device tree overlay
[24] https://spotpear.com/wiki/NVIDIA-Jetson-Orin-Nano-Developer-Kit-AI-40Top-RAM-8GB.html
[25] https://www.kernel.org/doc/Documentation/devicetree/bindings/iio/accel/adxl345.txt
[26] https://jetsonhacks.com/2020/05/04/spi-on-jetson-using-jetson-io/
[28] https://github.com/nagimov/adxl345spi
[29] https://github.com/YahboomTechnology/Jetson-ORIN-NANO-SUPER
-> Cool site~
[30] Exploring Raspberry Pi - Interfacing to the Real World with Embedded Linux®, Derek Molly, Wiley
[31] And, Google & Gemini~
Slowboot