はじめに
元々、Jetson Nanoを用いて開発など行っておりましたが、Jetson Xavier NXも使い始めたところで、入出力ポートの制御に関しても、同様の処理をXavier NXで実現する方法を調査およびテストしました。
(2021/12/31追記) Jetson TX2 NX moduleでも同様のGPIO制御の動作確認が取れましたので、TX2 NX関連の情報も追記します。(2022/01/04追記) Raspberry Pi 4の情報も追記しました。
その調査/テストが一区切りついたので、ここにレポートしておきます。
NanoにおけるGPIO制御とそのXavier NXへの移行
Jetson Nanoでは、最初、/sys/class/gpio経由でポートを制御していたが、一部、もう少し応答性を確保したいという事情から、
- 出力ポートのHIGH/LOW切替え
- 入力ポートの変化の検知
という部分は、mmap()にてSoCのGPIO制御レジスタのアドレスをマッピングして、そのレジスタを操作することで実現していた。
参考資料は以下に。
- Tegra X1 SoC Technical Reference Manualの「9.13 GPIO Registers」など
- valentis/jetson-nano-gpio-example - GitHub
そして、今回、Jetson Xavier NX への移行を検討する際も、GPIO制御レジスタのベースアドレスだけ変更すれば容易に移行できるだろうと考えていたところ、あれ?うまく行かない、ということで、色々調べてみた。
Xavier Series SoC Technical Reference Manualの「8.5.2.5 GPIO Ports and Controller Mapping」や「3.1.2 System Address Map」あたりを見ても、ベースアドレスだけは載っているが、いまいちGPIO制御レジスタの詳細なマッピングが見えて来ず。
色々としばらく調べていたところ、libgpiodに出くわした。
libgpiodを用いると...
libgpiodについてくるツールのgpiodetectとかgpioinfoを用いて、制御したいポートに対応する番号さえわかれば、あとは、比較的わかりやすいシンプルなI/FでGPIOが制御できた。
例えば、次のセクションで載せているソースコード内にて使用している、ヘッダピンのうちのPin12(I2S0_SCLK)とPin31(GPIO11)とPin40(I2S_DOUT)は、gpiochip0のline 157とline 134とline 158に対応することがわかる。
Pin12がI2S0_SCLKに、Pin31がGPIO11に、Pin40がI2S_DOUTに、それぞれ対応することは、NVIDIA Jetson Xavier NX GPIO Header Pinoutあたりのページを見ると分かる。
NVIDIAの公式な資料からは、Jetson Xavier NX Pinmux TableのI2S0_SCLKとGPIO11とI2S_DOUTの列AQのGPIO3 PT.05とGPIO3 PQ.06とGPIO3 PT.06から、8 x 19 + 5 = 157と8 x 16 + 6 = 134と8 x 19 + 6 = 158という数字が導かれる。
Jetson Nanoの場合は、Jetson Nano PinmuxのI2S0_SCLKとGPIO11とI2S0_DOUTの列HのGPIO3_PJ.07とGPIO3_PZ.00とGPIO3_PJ.06から、Pin12が8 x 9 + 7 = 79、Pin31が8 x 25 + 0 = 200、Pin40が8 x 9 + 6 = 78と分かる。
Jetson TX2 NXの場合は、Jetson TX2 NX Series Pinmux TableのI2S0_SCLKとGPIO11とI2S0_DOUTの列ARのGPIO3_PJ.00とGPIO3_PEE.02とGPIO3_PJ.01から、8 x 9 + 0 = 72と8 x 30 + 2 = 242と8 x 9 + 1 = 73という数字が得られる。ただし、TX2 NXの場合は、gpiodetectの結果が、
gpiochip0 [tegra-gpio] (192 lines)
gpiochip1 [tegra-gpio-aon] (64 lines)
gpiochip2 [max77620-gpio] (8 lines)
となることから、Pin31はgpiochip1のline 50となる。
ちなみに、Raspberry Piの場合は、Pin12(GPIO18)はline 18、Pin31(GPIO6)はline 6、Pin40(SCLK/GPIO21)はline 21、となっている。
(参考)ソースコード
今回、libgpiodを用いて書いたソースコードだけ、参考までに紹介しておく。
Pin31とPin12を接続してテスト。gpio_outputがPin31にH/L出力し、gpio_inputがPin12から入力し、Pin12の変化を検知したタイミングでPin40に出力している。
/*
* gpio_input.c
*/
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
#define GPIO_INPUT_LINE_OFFSET (445 - 288) // Pin12
#define GPIO_OUTPUT_LINE_OFFSET_2 (446 - 288) // Pin40
/* static関数のプロトタイプ宣言 */
static int check_key_input(void);
/* テストモード */
#define OUTPUT_ANOTHER_PORT_WHEN_DETECTING_INPUT
int main(int argc, char **argv)
{
const char *chipname = "gpiochip0";
const char *appname = "jetson_gpio_input_test";
struct gpiod_chip *chip;
struct gpiod_line *line_in, *line_out_2;
int val;
#ifdef OUTPUT_ANOTHER_PORT_WHEN_DETECTING_INPUT
int prev_val = -1;
#endif
// GPIOデバイスをオープンする
chip = gpiod_chip_open_by_name(chipname);
// 指定したGPIOポートのハンドラ取得
line_in = gpiod_chip_get_line(chip, GPIO_INPUT_LINE_OFFSET);
// ポート予約(入力設定)
gpiod_line_request_input(line_in, appname);
#ifdef OUTPUT_ANOTHER_PORT_WHEN_DETECTING_INPUT
// 指定されたGPIOポートのハンドラ取得
line_out_2 = gpiod_chip_get_line(chip, GPIO_OUTPUT_LINE_OFFSET_2);
// ポート予約(出力設定)
gpiod_line_request_output(line_out_2, appname, 0);
#endif
printf("Jetson GPIO Input Test: \nPin12: \n");
for (;;) {
#ifndef OUTPUT_ANOTHER_PORT_WHEN_DETECTING_INPUT
// GPIOポートの入力値取得
val = gpiod_line_get_value(line_in);
printf("%c", (val == 1) ? 'H' : 'L');
fflush(stdout);
usleep(200 * 1000);
#else
val = gpiod_line_get_value(line_in);
if (val != prev_val) {
gpiod_line_set_value(line_out_2, val);
printf("%c", (val == 1) ? 'H' : 'L');
fflush(stdout);
}
prev_val = val;
#endif
if (check_key_input()) // Enterキーの入力で抜ける
break;
}
// ポート予約の解除
gpiod_line_release(line_in);
#ifdef OUTPUT_ANOTHER_PORT_WHEN_DETECTING_INPUT
// ポート予約の解除
gpiod_line_release(line_out_2);
#endif
// GPIOデバイスをクローズする
gpiod_chip_close(chip);
return 0;
}
/**************************************************************
* check_key_input()
* キー入力チェック
* Enterキーまで入力されると検知される
**************************************************************/
static int check_key_input(void)
{
fd_set fds;
struct timeval timeout;
FD_ZERO(&fds);
FD_SET(0, &fds);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
return select(0+1, &fds, NULL, NULL, &timeout);
}
/*
* gpio_output.c
*/
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>
#define GPIO_OUTPUT_LINE_OFFSET (422 - 288) // Pin31
/* static関数のプロトタイプ宣言 */
static int check_key_input(void);
int main(int argc, char **argv)
{
const char *chipname = "gpiochip0";
const char *appname = "jetson_gpio_output_test";
struct gpiod_chip *chip;
struct gpiod_line *line_out;
int val = 0;
// GPIOデバイスをオープンする
chip = gpiod_chip_open_by_name(chipname);
// 指定したGPIOポートのハンドラ取得
line_out = gpiod_chip_get_line(chip, GPIO_OUTPUT_LINE_OFFSET);
// ポート予約(出力設定)
gpiod_line_request_output(line_out, appname, 0);
printf("Jetson GPIO Output Test: \nPin31: \n");
for (;;) {
val ^= 0x1;
// GPIOポートへの出力値設定
gpiod_line_set_value(line_out, val);
printf("%c", (val == 1) ? 'H' : 'L');
fflush(stdout);
//usleep(500 * 1000);
usleep(1000 * 1000);
if (check_key_input()) // Enterキーの入力で抜ける
break;
}
// ポート予約の解除
gpiod_line_release(line_out);
// GPIOデバイスをクローズする
gpiod_chip_close(chip);
return 0;
}
TX2 NXは、
#define GPIO_OUTPUT_LINE_OFFSET (242 - 192) // Pin31
const char *chipname = "gpiochip1";
と変更する。
おわりに
ということで、結局は、libgpiodを使っておけば、Jetsonシリーズ横断的に、基本的なGPIO制御ができますね、というお話しになりました。
また、元々、Jetsonシリーズの40ピンのヘッダピンは、Raspberry Piに合わせて用意されたとどこかで読んだ記憶がありますが、今回、Raspberry Pi 4でも同じコードで、ライブラリに渡すライン番号だけ、
#define GPIO_INPUT_LINE_OFFSET 18 // Pin12
#define GPIO_OUTPUT_LINE_OFFSET 6 // Pin31
#define GPIO_OUTPUT_LINE_OFFSET_2 21 // Pin40
と変更することで、無事に同じように動作したことをここに記しておきます。
libgpiod自体に関しては、Qiitaでは、libgpiodの使い方という記事がアップされていますし、libgpiodでググると結構、いろんなページが見つかります(らずぱいでIoT 第13回(C言語で制御するために、その4)やGPIO Programming: Exploring the libgpiod Library | ICS、など)ので、そちらをご参考にして下さい。
また、「たかがGPIO、されどGPIO」という感じで、libgpiodに行き着くまでに結構苦労した感じがしますので、その個人的な経緯はこちらにメモ書きしてみました。以上です。