メむンコンテンツぞスキップ
芋出し画像

Jetson Xavier NXのGPIOを䜿ったパルス速床を100倍速にしおみた

    IoTで甚いる゚ッゞコンピュヌタヌずしおはRaspberry Piが定番ですが、画像凊理を前提ずする堎合はNVIDIAのJetsonシリヌズの利甚が倚いのではないでしょうか。
    今回はJetsonシリヌズのなかでも、䟡栌性胜比の高いJetson Xavier NXでモヌタヌを回した際に調べたGPIOに぀いお曞いおいきたいず思いたす。

    モヌタヌ

    ステッピングモヌタヌ

    ゚ッゞコンピュヌタヌ偎でセンシングした結果ずしお、物理的な動きをさせたい堎合がありたす。そんな時、オヌプンルヌプ制埡で䜿えるステッピングモヌタヌがずおも䟿利です。

    ステッピングモヌタ自䜓に぀いおは、曞籍やりェブサむトに䞁寧な解説がありたすので、そちらをご芧いただくずしお、ここでは肝ずなるパルスに぀いお。

    パルス

    電気信号のレベルがH, Lず繰り返されるものです。
    このH, Lの1サむクルを1パルスず数えたす。ステッピングモヌタヌの堎合、1パルス入力される毎に1ステップ分の角床だけモヌタヌが回転したす。
    ぀たり、このパルスを綺麗にそしお自由に制埡するこずが、ステッピングモヌタヌを利甚するためには重芁なこずになりたす。

    マむクロステップ

    ステッピングモヌタヌは1パルスでステップ角床だけ回転するのですが、モヌタヌのコむルに流す電流を制埡するこずで、ステップ角床を现分化するこずができ、そういった制埡方法をマむクロステップ制埡ずいいたす。
    マむクロステップ制埡を行えるモヌタヌドラむバを䜿うためには、より高い呚波数でパルスを生成できるようにしおおく必芁がありたす。

    GPIOを䜿っおみる

    GPIO

    昚今の゚ッゞコンピュヌタヌ(あるいは、Single Board Computer)の倚くは、Raspberry Piの成功を受けおか40ピンのピンヘッダが蚭けられ、ピンにはSoC(System on Chip)の各皮機胜が割り圓おられおいたす。そしお、そこには「General Purpose Input/Output (GPIO)」ずいう汎甚入出力ポヌトが必ずず蚀っおいいほど割り圓おられおいたす。

    汎甚ず謳われおいる通り、蚭定次第で入力にも出力にも、プルアップ, プルダりン, スリヌステヌトや゚ッゞトリガヌ, レベルトリガヌなど、利甚目的にあった蚭定をするこずが可胜です。

    このGPIOをプログラム偎で適切に蚭定し駆動するこずで、前述のパルスを発生させるこずができたす。

    Jetson.GPIO

    Raspberry PiのPythonラむブラリで著名なRPi.GPIO互換のPythonラむブラリが、NVIDIAによっお䜜成され公開されおいたす。

    パッケヌゞでも存圚しおいお、以䞋のようにしおむンストヌルしたす。

    $ sudo apt install python3-jetson-gpio

    どの皋床のパルスを出せるか、簡単なコヌドで確認しおみたす(察象は12番ピンずしおいたす)。

    import RPi.GPIO as GPIO
    
    pin = 12
    
    def main():
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(pin, GPIO.OUT)
        try:
            while True:
                GPIO.output(pin, GPIO.HIGH)
                GPIO.output(pin, GPIO.LOW)
        finally:
            GPIO.cleanup()
    
    if __name__ == '__main__':
        main()

    たずは、Power modeを10w 2coreに蚭定しお蚈枬。呚波数は玄13.5kHzずさみしい結果に。

    画像
    Python: Power mode 10w 2core

    Power modeをCPUの駆動呚波数が䞀番高くなる20w 2coreにしおも玄16.8kHzず2割皋床の改善にずどたりたした。

    画像
    Python: Power mode 20w 2core

    C++でデバむスファむルを盎接I/O

    簡単にパルスを生成させるこずができたPythonですが、呚波数はかなり䜎めでした。そこで、C++でGPIOのデバむスファむルを盎接利甚しおみたす。

    自前でやる堎合は、12番ピンに察応するsysfsにおける番号を知る必芁がありたす。きちんず調べるべきですが、手を抜いお前述のPythonコヌドを実行しお調べたす。

    $ strace python3 gpio.py 

    䞊蚘のように実行し、/sys/class/gpio/export ファむルにどんな倀を曞き蟌んでいるかを確認したす。

    openat(AT_FDCWD, "/sys/class/gpio/export", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
    fstat(3, {st_mode=S_IFREG|0220, st_size=4096, ...}) = 0
    ioctl(3, TCGETS, 0x7fcfb2ee50)          = -1 ENOTTY (Inappropriate ioctl for device)
    lseek(3, 0, SEEK_CUR)                   = 0
    ioctl(3, TCGETS, 0x7fcfb2ee50)          = -1 ENOTTY (Inappropriate ioctl for device)
    lseek(3, 0, SEEK_CUR)                   = 0
    lseek(3, 0, SEEK_CUR)                   = 0
    write(3, "445", 3)                      = 3
    

    「445」を曞き出しおいるので、12番ピンに察応するデバむスファむルは「/sys/class/gpio/gpio445」配䞋のファむルずなりたす。
    この情報をもずに、パルスを生成するコヌドです。

    #include <cstdint>
    #include <cstring>
    #include <fcntl.h>
    #include <iostream>
    #include <signal.h>
    #include <stdexcept>
    #include <sys/param.h>
    #include <unistd.h>
    
    class GPIOPin {
    public:
        GPIOPin(int pin) : pin_(pin), value_fd_(0), direction_fd_(0) {
            try {
                export_pin(pin_);
            } catch (std::exception& e) {
                throw;
            }
            char value_path[MAXPATHLEN];
            char direction_path[MAXPATHLEN];
            snprintf(value_path, sizeof(value_path), "/sys/class/gpio/gpio%d/value", pin_);
            snprintf(direction_path, sizeof(direction_path), "/sys/class/gpio/gpio%d/direction", pin_);
            value_fd_ = open(value_path, O_RDWR);
            direction_fd_ = open(direction_path, O_RDWR);
        }
        ~GPIOPin() {
            try {
                unexport_pin(pin_);
            } catch (std::exception& e) {
                std::cerr << e.what() << std::endl;
            }
        }
        void setup(bool output) {
            if (output) {
                write(direction_fd_, "out\n", 4);
            } else {
                write(direction_fd_, "in\n", 3);
            }
        }
        void high() { write(value_fd_, "1\n", 2); }
        void low() { write(value_fd_, "0\n", 2); }
    
    private:
        void export_pin(int pin) {
            const char *kEXPORT_PATH = "/sys/class/gpio/export";
            int fd = open(kEXPORT_PATH, O_WRONLY);
            char msgbuf[32];
            snprintf(msgbuf, sizeof(msgbuf), "%d\n", pin);
            write(fd, msgbuf, strlen(msgbuf));
            close(fd);
        }
        void unexport_pin(int pin) {
            const char *kUNEXPORT_PATH = "/sys/class/gpio/unexport";
            int fd = open(kUNEXPORT_PATH, O_WRONLY);
            char msgbuf[32];
            snprintf(msgbuf, sizeof(msgbuf), "%d\n", pin);
            write(fd, msgbuf, strlen(msgbuf));
            close(fd);
        }
    
    private:
        int pin_;
        int value_fd_;
        int direction_fd_;
    };
    
    volatile int interrupted = 0;
    
    void signal_handler(int signo) {
        interrupted = 1;
    }
    
    int main(int argc, char** argv) {
        try {
            signal(SIGINT, signal_handler);
            GPIOPin pin(445);
    
            pin.setup(true);
            while (!interrupted) {
                pin.high();
                pin.low();
            }
        } catch (std::exception& e) {
            std::cout << e.what() << std::endl;
        }
    }

    Power modeを10w 2coreず20w 2coreで行ったずころ、それぞれ玄287kHz, 箄326kHzず20倍ぐらい改善したした。

    画像
    C++: Power mode 10w 2core
    画像
    C++: Power mode 20w 2core

    もっず高速に

    GPIOはOS(今回はLinux)が管理しおいる資源で、OSを経由しおGPIOを利甚する限りはこれ以䞊の速床改善が芋蟌めたせん。
    ナヌザヌ空間のプログラムでさらなる改善を目指す堎合の手法ずしお、GPIO関連の制埡レゞスタをmmap(2)でナヌザヌ空間にマップし、そのレゞスタを盎接操䜜するずいうものがありたす。

    Raspberry PiやJetson Nanoは利甚ナヌザヌが倚いため、レゞスタのアドレスや蚭定などの情報は簡単にみ぀かりたすが、Jetson Xavier NXはナヌザヌが少ないようで情報が芋぀かりたせんでした。

    ここでは、関連する制埡レゞスタなどの探し方を曞いおいきたいず思いたす。

    調査方法

    必芁な情報の収集

    Jetson Download Centerに行き、いく぀かの資料を入手したす。

    • Xavier Series SoC Technical Reference Manual (Xavier_TRM_DP09253002_v1.4p.pdf)

    • Jetson Xavier NX Developer Kit Carrier Board Specification(Jetson_Xavier_NX_DevKit_Carrier_Board_Specification_v1.0.pdf)

    • Jetson Xavier NX Pinmux Table(Jetson_Xavier_NX_Pinmux_Configuration_Template_v1.06.xlsm)

    資料のほかに゜ヌスコヌド等も入手しおおきたす。

    • Driver Package (Jetson_Linux_R35.2.1_aarch64.tbz2)

    • Driver Package (BSP) Sources (public_sources.tbz2)

    12番ピン

    今回は12番ピンを䜿いたすので、たずは䜕が割り圓おられおいるかをJetson_Xavier_NX_DevKit_Carrier_Board_Specification_v1.0.pdfの「3.3 40-Pin Expansion Header」にある図ず衚から確認したす。

    • 「Figure 3-1. Expansion Header Connections」

    • 「Table 3-3. Expansion Header Pin Description – J12」

    12番ピンは「I2S0_SCLK」ずいう名前ですが、SoCずしおは「DAP5_SCLK」ずいうピンになっおいる。暙準では、耇数機胜のうちGPIOが割り圓おられおいお、「PT.05」ずいうGPIOが察応しおいるこずがわかりたす。

    耇数機胜からGPIOを遞択するためのレゞスタのアドレス、GPIOで出力をできるようにするためのレゞスタのアドレス、そしお、適切な動䜜をさせるための蚭定方法を調べるこずになりたす。

    レゞスタのアドレス

    正しい手順はXavier_TRM_DP09253002_v1.4p.pdfの以䞋の章を読んでなのですが、ここでは手を抜いた方法を蚘茉したす。

    • 3.1.2 System Address Map

    • 8.5.3 Programming Guidelines

    手順ずしおは以䞋のようになりたす。

    1. Jetson_Xavier_NX_Pinmux_Configuration_Template_v1.06.xlsmでDevice Tree甚の情報を生成

    2. Driver Packageに含たれおいるスクリプトで機胜割り圓お(PinMux)のレゞスタのアドレスずGPIOのレゞスタのアドレスを生成

    Jetson_Xavier_NX_Pinmux_Configuration_Template_v1.06.xlsmのシヌト「Jetson_Xavier_NX_Module」を開きたす。12番ピンの名前「I2S0_SCLK」の行を芋るず、Carrier BoardのPDFファむルに蚘茉されおいたように、GPIOずしお「GPIO3_PT.05」が割り圓たるずなっおいたす。

    PinMux甚情報の生成は、AV列付近にある「Generate DT File」を抌䞋したす。ポップアップりむンドりが出お「Would you like to generate device tree file for pinmux table?」ず聞かれたすので「はい」を抌䞋したす。
    その埌「Board Name」が二回聞かれたすので、適圓な同じ名前を二回入力するず、以䞋の3ファむルが生成されたす。

    • tegra19x-ボヌド名-gpio-default.dtsi

    • tegra19x-ボヌド名-padvoltage-default.dtsi

    • tegra19x-ボヌド名-pinmux.dtsi

    次は、Driver PackageのJetson_Linux_R35.2.1_aarch64.tbz2を展開し、「Linux_for_Tegra/kernel/pinmux/t19x」にある倉換スクリプトpinmux-dts2cfg.pyを先ほど生成されおたファむルを指定しお実行したす。スクリプトの䜿い方は、同じディレクトリヌ内のREADME.txt に曞かれおいたす。

    $ bzip2 -cd Jetson_Linux_R35.2.1_aarch64.tbz2 | tar xf -
    $ cd Linux_for_Tegra/kernel/pinmux/t19x
    $ python pinmux-dts2cfg.py --pinmux addr_info.txt gpio_addr_info.txt por_val.txt --mandatory_pinmux_file mandatory_pinmux.txt tegra19x-XXXX-pinmux.dtsi tegra19x-XXXX-gpio-default.dtsi 1.0 > pinmux.cfg

    スクリプトが幟぀かの゚ラヌを報告したすが、気にせずリダむレクトしたファむルを開き、「GPIO3_PT.05」に察応する郚分を参照したす。

    pinmux.0x022138a0 = 0x00000001; # CONFIG T5
    pinmux.0x02431080 = 0x00000000; # GPIO dap5_sclk_pt5

    䞊蚘のように出力されおいるず思いたす。
    コメントずしお「CONFIT T5」が぀いおいる方がGPIOを制埡するためのレゞスタのアドレス、「GPIO dap5_sclk_pt5」のコメントが぀いおいる方がPinMuxのレゞスタのアドレスずなりたす。

    レゞスタぞの蚭定倀

    レゞスタのアドレスはわかりたしたが、所望の動䜜をさせるために䜕を曞き蟌めば良いかです。

    PinMuxの方は、説明を省きたすがXavier_TRM_DP09253002_v1.4p.pdfの「8.5.4.2 Audio PAD Control Registers」にある「PADCTL_AUDIO_DAP5_SCLK_0」がそれに圓たり、出力甚のGPIOずしお機胜させるためには各ビットを以䞋のようにしたす。

    • シュミットトリガヌ: DISABLE

    • GPIOずSFIOの遞択: GPIO

    • 入力: DISABLE

    • トラむステヌト: PASSTHROUGH

    • プルアップ,プルダりン指定: NONE

    • PM指定: デフォルトのI2S5

    結局、0x00000000(0bxxxx,xxxx,xxxx,xxxx,xxx0,x0x0,x0x0,0000)ずなりたす。

    GPIOの方は、Driver Package (BSP) Sourcesを展開し、その䞭のgpio-tegra186.cを参照したす。
    問題のgpio-tegra186.cは、Linux_for_Tegra/source/public/kernel/kernel-5.10/drivers/gpio内にありたす。

    $ bzip2 -cd public_sources.tbz2 | tar xf -
    $ cd Linux_for_Tegra/source/public/kernel/kernel-5.10/drivers/gpio
    $ ls gpio-tegra186.c

    関数tegra186_gpio_direction_outputを読むず、出力できるようにするには
    アドレスTEGRA186_GPIO_OUTPUT_CONTROL(0x0C)の
    ビットTEGRA186_GPIO_OUTPUT_CONTROL_FLOATED(0x00)を0にしお
    アドレス+TEGRA186_GPIO_ENABLE_CONFIG(0x00)の
    ビットTEGRA186_GPIO_ENABLE_CONFIG_ENABLE(0x0)ず
    ビットTEGRA186_GPIO_ENABLE_CONFIG_OUT(0x1)の二぀を1にすればよいず分かりたす。

    関数tegra186_gpio_setを読むず
    アドレス+TEGRA186_GPIO_OUTPUT_VALUE(0x10)の
    ビットTEGRA186_GPIO_OUTPUT_VALUE_HIGH(0x0)を1ずするずHが
    ビットTEGRA186_GPIO_OUTPUT_VALUE_HIGH(0x0)を0ずするずLが出力されるず分かりたす。

    実行結果

    実装コヌドは長くなるので割愛したすが、レゞスタを適宜マップし、レゞスタに察しお所定の倀を曞き蟌むようにしお実行したした。

    箄1.37MHzずなり、Pythonラむブラリに察しお玄100倍たで改善したした。ちなみに、Power mode 10w 2core ず20w 2coreで差はなく、ボトルネックがCPUではなくなっおいたす。

    画像
    レゞスタアクセス: Power mode 20w 2core

    最埌に

    実際の䜿甚にあたっおは、゜フトりェア的にぱラヌハンドリングや埌凊理が必芁です。たた、ハヌドりェア的にはそもそもCarrier Boardから出おいるピンは、モヌタヌドラむバヌを駆動するだけの電流を流せたせんので、モヌタヌドラむバヌのドラむバヌなどを甚意する必芁がありたす。
    しかしながら、Jetson_Xavier_NX_Pinmux_Configuration_Template_v1.06.xlsmでピンぞの割り圓おずしおGPIOを遞択しおファむルを生成するこずで、同様の手順で情報を入手し利甚するこずが可胜ずなりたす。

    説明を端折ったり、提䟛されおいるマクロやスクリプトの利甚を前提ずしおいろいろず省いたりしおしたいたしたが、Jetson Xavier NXのGPIOを制埡するための情報ずなれば幞いです。


    あなたぞのおすすめ