Jetson Orin NanoとFPGAボード間でのシリアル通信 from エンジニア・メモ 記者:早川喜太

目次

Jetson Orin NanoとFPGAボード間でのシリアル通信 from エンジニア・メモ

『第3回名古屋スマート物流EXPO』<10/25(木)〜27(金)>では、自社のAMR展示だけでなく、他社のAGVをAMR化するソリューションの展示を予定しております。そこでAGVの制御基板と、当社が用意するJetson Orin Nano間でシリアル通信する必要が生じたため、、エンジニアの開発メモを共有いたします。

はじめに

AMRの開発を進めるに当たり、Jetson Orin NanoとFPGAボード間でシリアル通信する必要が生じた。
新しく学ぶことが多かったため、今後のために記事形式で残す。

開発環境

Jetson Orin Nano

  • Jetpack 35.4.1
  • Ubuntu20.04

FPGAボード

ARTY A7とは

今回、Digilent社のARTY A7-35TというFPGAボードを使用した。(現在は生産終了している)
ARTY A7は、XILINXの7シリーズFPGAの一つであるArtix-7 FPGAを搭載している。
このFPGAボードをMicro-USB(A-MicroB)ケーブルで接続することで、

  1. FPGAにアクセスするためのJTAG
  2. UART(シリアル通信)

の2つの機能を利用できる。

デバイスの認識とアクセス権

Ubuntu20.04では、ftdi_sioというドライバーが標準でインストールされており、接続するだけで自動でデバイスが認識される。

デバイスの認識に関してだが、USB機器が一つも接続されていない状態でARTY A7を接続すると、
/dev/に、FIFOとUARTに対応するデバイスが それぞれttyUSB0, ttyUSB1として追加される。
(確認する際は $ ls -l /dev/tty* で接続前後でリストの変化を調べること)

ここで一つの問題が生じる。ttyUSB0とttyUSB1はアクセスの際にroot権限が必要となる。
プログラムからアクセスする際に困るので、以下のコマンドでユーザをdialoutグループに追加することでフルアクセスできるようにする。

$ sudo adduser $USER dialout

なお、グループ情報を反映させるためには再起動が必要となる。

デバイスの管理

USB接続機器がたくさんある中でデバイスをUSBで接続すると、ttyUSB0, ttyUSB1と連番でデバイスが認識されるので、どのデバイスがFPGAボードなのか確認する作業が必要となる。

そこで、FPGAボードが接続された際にFPGAボードにアクセス可能なシンボリックリンク(WindowsOSでいうショートカット)を自動で生成する仕組みを考える。
これを実現するため、udevのルール設定を行う。

udevとは、Linuxシステムでデバイスの管理をする仕組み。
Ubuntuでは、/etc/udev/rules.dディレクトリにルールファイルが置かれている。

今回は、/dev/ttyUSB1がUARTっぽい(特定方法は割愛)ので、udevadmコマンドを利用して詳細情報を確認する。

$udevadm info [デバイス名]

結果は以下。

$ udevadm info /dev/ttyUSB1

P: /devices/platform/3610000.xhci/usb1/1-2/1-2.4/1-2.4:1.1/ttyUSB1/tty/ttyUSB1
N: ttyUSB1
L: 0
S: serial/by-id/usb-Digilent_Digilent_USB_Device_210319788424-if01-port0
S: serial/by-path/platform-3610000.xhci-usb-0:2.4:1.1-port0
S: ttyUSB-FT232R
E: DEVPATH=/devices/platform/3610000.xhci/usb1/1-2/1-2.4/1-2.4:1.1/ttyUSB1/tty/ttyUSB1
E: DEVNAME=/dev/ttyUSB1
E: MAJOR=188
E: MINOR=1
E: SUBSYSTEM=tty
E: USEC_INITIALIZED=2050102694
E: ID_VENDOR=Digilent
E: ID_VENDOR_ENC=Digilent
E: ID_VENDOR_ID=0403
E: ID_MODEL=Digilent_USB_Device
E: ID_MODEL_ENC=Digilent\x20USB\x20Device
E: ID_MODEL_ID=6010
E: ID_REVISION=0700
E: ID_SERIAL=Digilent_Digilent_USB_Device_210319788424
E: ID_SERIAL_SHORT=210319788424
E: ID_TYPE=generic
E: ID_BUS=usb
E: ID_USB_INTERFACES=:ffffff:
E: ID_USB_INTERFACE_NUM=01
E: ID_USB_DRIVER=ftdi_sio
E: ID_VENDOR_FROM_DATABASE=Future Technology Devices International, Ltd
E: ID_MODEL_FROM_DATABASE=FT2232C/D/H Dual UART/FIFO IC
E: ID_PATH=platform-3610000.xhci-usb-0:2.4:1.1
E: ID_PATH_TAG=platform-3610000_xhci-usb-0_2_4_1_1
E: ID_MM_CANDIDATE=1
E: DEVLINKS=/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319788424-if01-port0 /dev/serial/by-path/platform-3610000.xhci-usb-0:2.4:1.1-port0 /dev/ttyUSB-FT232R
E: TAGS=:systemd:

詳細情報から、FPGAボードの特定に必要な情報として、下記4項目に着目する。

  • E: SUBSYSTEM=tty
  • E: ID_VENDOR_ID=0403
  • E: ID_MODEL_ID=6010
  • E: ID_SERIAL_SHORT=210319788424

上記4項目を条件とし、条件に合致するデバイスが追加された際にシンボリックリンクを作成するというルールファイルを作成する。

まず、ルールファイルを新規生成(システムフォルダ上の作業なのでroot権限が必要)

$ sudo touch /etc/udev/rules.d/99-uart.rules

次にルールファイルの中身を記述する。
今回は”ttyUSB-ARTY-A7″というシンボリックリンクを作成する仕様とした。

USB-UART System (FT232R)
SUBSYSTEM==”tty”, ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6010″, ATTRS{serial}==”210319788424″, SYMLINK+=”ttyUSB-ARTY-A7″, GROUP=”dialout”

最後に、以下のコマンドでudevルールを更新

$ udevadm control --reload-rules

これで、ルールが適用されるようになる。

通信テスト(catコマンド)

今回、協力者に頼んで、FPGAにループバック(受信したデータをそのまま返信する)を実装してもらった。catコマンドで簡単に確認できるということで、通信テストを行った。

結論から言うと、catコマンドは仕様上うまく確認できないが、ループバックの挙動らしい痕跡は確認できた。

ターミナル1

ターミナルを立ち上げ、以下のコマンドを実行し、受信待ち状態とする。(受信を止める場合はCtrl+C)

$ cat /dev/ttyUSB-ARTY-A7

ターミナル2

別のターミナルを立ち上げ、以下のコマンドを実行し、文字列”test”を送信する。

$ echo 'test' > /dev/ttyUSB-ARTY-A7

ターミナル1側で受信したデータが表示されるのを期待したが、
結果としては、空行含みのデータを延々と取得し続ける。
理由は簡単で、本来はバイナリデータを送信すべきところを普通の文字列で送信しているからだ。

一般的にはscreenやminicomを使うという話。

信テスト(pyserial)

pythonのパッケージの一つにpyserialがある。
これを利用すると、シリアル通信が可能になる。

以下のコマンドでインストール

$ pip install pyserial

作成したスクリプトを以下に示す。

# pyserialはインポートする際はserial
import serial
# 初期設定
# ポート名とボーレートを設定
ser = serial.Serial('/dev/ttyUSB-ARTY-A7', 115200)
# データ送信 バイナリデータを示すようb文字列で
ser.write(b'test')
# データ受信
rxdata_b = ser.read_all() # バイナリデータであることに注意
# 文字列型にデコード
rxdata = rxdata_b.decode('utf-8')
print(f"received data: {rxdata})

また、上のスクリプトとは別でpython対話モードで試した結果を以下に示す。

参考

【シリアル通信】PySerialの基本的な使い方【Python】

目次