コスモリサーチの凄腕エンジニアたちが日々、難題と格闘した記録
Copyright Cosmo Research Corp.

USB_gadgetで2つのデバイスを同時に見せる on Zynq

参考資料

やること

Linux Kernel Config

比較的新しいconfigfsでのUSB Gadget制御の仕組みを使うため、以下を有効にする。
その他必要な設定はおそらくデフォルトで有効になっているが、不安な場合はZynq Linux USB Device Driverを確認のこと。

Device Drivers > USB support > USB Gadget Support
    <M>   USB functions configurable through configfs
        [*]     RNDIS
        [*]     Mass storage

Device Tree Source

  • dr_mode: peripheralに変更(OTGでも大丈夫かもしれないが未確認)
  • usb-phy: 指定が必要
--- a/xsdk/devicetree_bsp/pcw.dtsi   Tue Jul 11 11:08:17 2017 +0900
+++ b/xsdk/devicetree_bsp/pcw.dtsi    Fri Sep 15 20:48:37 2017 +0900
@@ -82,12 +82,24 @@
    status = "okay";
 };
 &usb0 {
-  dr_mode = "host";
+   dr_mode = "peripheral"; // for USB Gadget change from "host"
    phy_type = "ulpi";
    status = "okay";
    usb-reset = <&gpio0 7 0>;
+   usb-phy = <&usb_phy0>; // for USB Gadget
 };
 &clkc {
    fclk-enable = <0x1>;
    ps-clk-frequency = <33333333>;
 };
+
+// for USB Gadget
+/ {
+   usb_phy0: phy0 {
+       compatible = "ulpi-phy";
+       #phy-cells = <0>;
+       reg = <0xe0002000 0x1000>;
+       view-port = <0x170>;
+       drv-vbus;
+   };
+};
\ No newline at end of file

Gadgetの有効化

以下(usb_config_multi.sh)を実行する。出来る限りコメントを書いた。

#!/bin/sh

# https:/github.com/torvalds/linux/blob/master/Documentation/usb/gadget_configfs.txt
# http:/irq5.io/2016/12/22/raspberry-pi-zero-as-multiple-usb-gadgets/
# 上記2つを参考に同時に二つのガジェットを有効化する。
#   1. mass storageで、SDカードをUSBメモリとして見せる
#   2. rndisで、USBをetherデバイスとして見せる

# ドライバを依存関係含めてロード
modprobe usb_f_rndis
modprobe usb_f_mass_storage

# configfsをマウント
mount -t configfs none /sys/kernel/config

# usb_gadgetの下に任意のディレクトリを生成する
g=/sys/kernel/config/usb_gadget/multi
mkdir ${g}

# USBの各種設定(VID/PIDは必須)
echo "64"     > ${g}/bMaxPacketSize0
echo "0x200"  > ${g}/bcdUSB    # USB2.0
echo "0x100"  > ${g}/bcdDevice # 適当
echo "0x03FD" > ${g}/idVendor  # Xilinx
echo "0x0104" > ${g}/idProduct # Multifunction Composite Gadget

# 複数functionのcomposite USB向けの設定
# refer: https://msdn.microsoft.com/en-us/library/windows/hardware/ff540054.aspx
echo "0xEF"   > ${g}/bDeviceClass
echo "0x02"   > ${g}/bDeviceSubClass
echo "0x01"   > ${g}/bDeviceProtocol

# functionsに登録
mkdir ${g}/functions/rndis.rn0
mkdir ${g}/functions/mass_storage.ms0

# rndis固有の設定(設定しないとランダムなmacアドレスを生成する)
# echo "${dev_mac}"  > ${g}/functions/rndis.rn0/dev_addr
# echo "${host_mac}" > ${g}/functions/rndis.rn0/host_addr

# mass storage固有の設定
# fileにストレージとして見せるデバイスを指定する
echo /dev/mmcblk0p1 > ${g}/functions/mass_storage.ms0/lun.0/file
echo 1              > ${g}/functions/mass_storage.ms0/lun.0/removable

# functionとconfigを関連付け
mkdir ${g}/configs/c.1
ln -s ${g}/functions/rndis.rn0        ${g}/configs/c.1/
ln -s ${g}/functions/mass_storage.ms0 ${g}/configs/c.1/

# rndisをwindowsで見えるようにするための設定
echo "1"       > ${g}/os_desc/use
echo "0xcd"    > ${g}/os_desc/b_vendor_code
echo "MSFT100" > ${g}/os_desc/qw_sign
echo "RNDIS"   > ${g}/functions/rndis.rn0/os_desc/interface.rndis/compatible_id
echo "5162001" > ${g}/functions/rndis.rn0/os_desc/interface.rndis/sub_compatible_id
ln -s ${g}/configs/c.1 ${g}/os_desc

# デバイス有効化
echo "ci_hdrc.0" > ${g}/UDC

Gadgetの動作確認

Mass Storageは接続するだけでSDカードの中身が見える。

Etherは、Zynq側で以下を実行しておくとPCで接続したときにネットワークデバイスとして見える。

ifconfig usb0 192.168.1.2 # 好きなIPアドレスを設定
ifconfig usb0 up          # network起動

Gadgetの削除

以下(usb_remove_multi.sh)を実行する。Gadgetの有効化で使用したusb_config_multi.shと対応している。

#!/bin/sh

# https://github.com/torvalds/linux/blob/master/Documentation/usb/gadget_configfs.txt
# http://irq5.io/2016/12/22/raspberry-pi-zero-as-multiple-usb-gadgets/
# 上記を参考にusb_config_multi.shで追加したガジェット2つを無効化する。

# ガジェットを無効化
g=/sys/kernel/config/usb_gadget/multi
echo "" > ${g}/UDC

# functionsのsimlink先を削除
rm ${g}/os_desc/c.1
rm ${g}/configs/c.1/rndis.rn0
rm ${g}/configs/c.1/mass_storage.ms0

# Configurationsを削除
rmdir ${g}/configs/c.1/

# functionsのsimlink元を削除
rmdir ${g}/functions/rndis.rn0
rmdir ${g}/functions/mass_storage.ms0

# gadget本体を削除
rmdir ${g}

# ドライバを依存関係含めてアンロード
modprobe -r usb_f_rndis
modprobe -r usb_f_mass_storage

# configfsをアンマウント
umount /sys/kernel/config

MicroBlazeで割り込みのネストを行う

 ベアメタルのMicroBlazeを用いたデザインの実装中に、QSPIメモリにアクセスするとソフトがフリーズしてしまう現象に遭遇した。 詳しく解析してみると、どうやら外部スイッチの割り込みの処理中にQSPIメモリへアクセスすると確定で再現するようであった。 QSPIメモリへのアクセス時には割り込み処理が発生することから割り込みのネストあたりが原因だろうと検討をつけて調べたところ、 割り込みのネストを行うために必要な設定がされていなかったことがわかった。

 MicroBalzeで割り込みのネストを行うためには、下記2つの設定を行う必要があるようだ。

1. 2回目に発生する割り込み信号の優先度を上げておく。

 割り込み信号は下位bitの方が優先度が高く、割り込みをネストする際はより優先度の高い割り込みを行う必要がある。 動作確認した系では割り込み信号の[0]をQSPIの割り込み信号、[2]を外部スイッチの割り込み信号としている。

f:id:cosmo-blog-admin:20200817171820p:plain

2. AXI Interrupt ContorollerのInterrupt Level Registerを有効化する。

 IPの設定を開いてAdvancedタブを見るとデフォルトではEnable Interrupt Level Registerのチェックが外れているため、このチェックを入れる。

f:id:cosmo-blog-admin:20200817171929p:plain

 上記2点をちゃんと設定して再度動作確認したところ、QSPIアクセス時のフリーズが発生しなくなった。

USB-UART(+JTAG)ICで起動直後のログが見えない

Zynqと接続した、JTAG-SMT3-NC/DIGILENTのUART出力が起動ログの途中からしか出てこない問題の解析を行った。

症状

  • fsblのログは表示されず、u-bootの途中からになる。
  • 他プロジェクトでも同様の症状
  • 表示されないのは電源投入した際で、ハードウェアリセットの場合は、fsblのログも表示される。
  • USB-UARTのデバイスCY7C65211-24LTXI/Cypress ではfsblの最初から表示される。

原因

CY7C65211-24LTXIはVBUSで駆動するため、装置の電源投入前からPCがUARTをUSBデバイスとして認識できる。 JTAG-SMT3-NCはVBUS_DETだけでVBUS駆動ではないので、装置の電源投入後にPCがUSBデバイスの接続が始まる。 USBデバイスの認識は数秒かかるので、装置起動後の最初のほう(fsbl~)はUART転送可能になる前に終わってしまい、 ログとして残らない。 JATGの接続も起動直後は同様であるため、起動時の問題デバッグなどができない。

装置へのコマンド転送などが目的であれば実用上問題になることはないが、 デバッグ用途に使用する場合、UART/JTAGから起動時のログを見ることがあるので、JTAG-SMT3-NCのみでJTAG、UARTを接続するのはよろしくない。

対策

JTAG-SMT3-NCのVDD(15pin)をVBUSから取ってあげればよい。 DIGILENTのフォーラムに可能であるとの回答があった。
https://forum.digilentinc.com/topic/17862-jtag-smt3-nc/#comment-45773

USBコネクタ一つでUART/JTAGが使えれば、PCとのIF、部品点数を減らせてうれしい。 ただしVBUS駆動の実験などはまだ実施していない。

pthreadの仮想メモリ解放

LinuxPosix Thread(pthread)で仮想メモリが解放されない時があるので 条件を調べてみました。 環境はZYNQのLinuxなのでPCのLinuxとは動作が違う部分があるかもしれません。

結果的にはpthread_createで作成したスレッドではスレッド終了時には仮想メモリを解放されませんが、 再度pthread_createをした時に前回確保したメモリを再利用しているようです。 (=新たな確保をしていない)

pthread_createで確保されるメモリはスレッドのスタックサイズに依存するのですが 初期状態だと8MBがスタックサイズになっているので組み込みのプログラムとしては大量の メモリを使用しているように見えてしまいます。 解放しないのは仮想メモリだけで物理メモリが解放されず残っているわけではないです。 プログラム動作的には問題ないのですがPSコマンドやtop/htopコマンドを使用してメモリ使用量を見るとき、 スレッドでで自動的に確保されるサイズが加算されて見えるていることには注意しないといけません。

動作例

スレッドを6つ作成、終了後、新たにスレッドを2つ作成するプログラムです。 6つスレッドが終了するとpthread_createで確保されたメモリは全て解放される動作を期待するのですが、 実際に動かしてみると6つのスレッドが終了した時点で仮想メモリが確保されたままになります。

メモリの使用量がわかるよう動かしている途中でコンソールからhtopコマンドを使用して VIRT(仮想メモリ)とRES(物理メモリ)を見てみます。

ステップ1  初回のpthread_create

スレッドを6個作成すると1スレッド当たり約8MB程度、6スレッド作成完了した時点でこのプログラムでは 50MBのメモリが仮想メモリとして確保されます。 6つのスレッドが終了し、joinsした段階でメモリは解放されるはずですが、topのVIRTを見るとまだ42Mbyte程度使用しています。

ステップ2  2回目以降のpthread_create

この後、2つのスレッドを再度作成するとVIRTは42MBのままです。 これは前回確保した仮想メモリを流用しているため、新たなメモリ確保が行われてなかったものと考えられます。 2つのスレッドが終了後もVIRTは42MBを維持し、プログラム終了で解放されます。

//スレッド本体関数
void *func_null(void *arg){
    printf("new thread created\n");
    sleep(10);
}


int main()
{
    pthread_t th_cli1;
    pthread_t th_cli2;
    pthread_t th_cli3;
    pthread_t th_cli4;
    pthread_t th_cli5;
    pthread_t th_cli6;

    pthread_attr_t th_cli_attr;
    int status;


    //--------------------------------------------------------------------------------//
    // スレッド属性を作ってスレッド作成の準備
    //--------------------------------------------------------------------------------//
    pthread_attr_init(&th_cli_attr);
    //--------------------------------------------------------------------------------//
    // TEST1 スレッドを6個作成
    //--------------------------------------------------------------------------------//
    status = pthread_create(&th_cli1,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli2,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli3,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli4,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli5,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli6,&th_cli_attr,func_null,(void*)0);

    pthread_join(th_cli1,NULL);
    pthread_join(th_cli2,NULL);
    pthread_join(th_cli3,NULL);
    pthread_join(th_cli4,NULL);
    pthread_join(th_cli5,NULL);
    pthread_join(th_cli6,NULL);

    printf("6 thread finished\n");
    sleep(10);
    //--------------------------------------------------------------------------------//
    // TEST2 スレッドを2個作成
    //--------------------------------------------------------------------------------//
    status = pthread_create(&th_cli1,&th_cli_attr,func_null,(void*)0);
    status = pthread_create(&th_cli2,&th_cli_attr,func_null,(void*)0);

    pthread_join(th_cli1,NULL);
    pthread_join(th_cli2,NULL);
    printf("2 thread finished\n");
    sleep(10);
    return;
}

【社内向け情報共有例】3Dプリンタの使い方

以前コスモリサーチ社内向けに展開した3Dプリンタ使用方法の簡易的な手順を記載する.
これは一例であるが,社内では情報共有を積極的に行っている.
なおこの内容単体ですぐ3Dプリンタを使用できる程の情報量にはなっていないので注意.


・BCN3D”Sigma R19”について
https://dddjapan.com/products/bcn3d-sigma-r19

・使用の手順
① 制作する形の検討
② 3D CADで設計
③ ファイルの変換
④ 3Dプリント

① 制作する形の検討

f:id:cosmo-blog-admin:20200525184834j:plain

まず、紙ベースでどんな形にするか決める.
詳細な寸法は一度プリントして現物で確認したほうが良いので,悩むくらいなら暫定で決めてしまったほうが良い.

以下,ポイント箇条書き

  • 3D CADは始めると楽しくなってしまうので,発散しないように事前に紙ベースで検討を⾏う.
  • 45°以上のオーバーハングはサポートがないと製造できないため,積層方向を意識して検討する
  • 体積が大きいと値段や印刷時間が増えるので可能なら小さく作る.
  • 高さ方向に伸ばすと印刷時間が延びるので,可能なら低くするor短い方向に積層する.

② 3D CADで設計

STL形式で出⼒できれば何でもいいが,初心者でも使いやすいRSコンポーネンツの無料3D CAD”DesignSpark Mechanical”がおすすめである.
https://www.rs-online.com/designspark/mechanical-software-jp
STEPデータの読み込みとSTLファイルの書き出しが可能で,ググって情報も拾いやすい.

以下,DesignSparkのポイント箇条書き

  • STEP書き出し不可
  • 図形への文字の書き込みができない
  • 修正がSolidWorksに比べて難しい.
  • AltiumのSTEPデータを読み込めるが,AltiumのSTEP出力には全部品の外形とvia等が付いてくるので重くなりやすい. PCBファイルをSTEP出力用にコピーし,viaを削除する,部品点数が多い場合は必要のない実装部品は消すなどした方が良いかもしれない.

③ ファイルの変換

STL形式で出⼒したファイルを3Dプリンタ付属の”BCN3D Cura”で読み込みGcodeに変換する.
https://www.bcn3d.com/bcn3d-cura/

④3Dプリント

  1. ベースガラスが汚れていたら水で洗いきれいにする
  2. 平⾯出しのキャリブレーションを⾏う
  3. ベースプレートを加熱する(10分程度)
  4. GcodeをSDカードから3Dプリンタに読み込ませる
  5. 印刷する

3Dプリンタの使用例

・基板スペーサー

f:id:cosmo-blog-admin:20200525184915j:plain f:id:cosmo-blog-admin:20200525184929j:plain f:id:cosmo-blog-admin:20200525184947j:plain

筐体に基板を取り付けるためのスペーサー.
両面テープとホットボンドで取り付ける.
もともとはゴムシートを切って貼っていたがその手間が削減,また位置合わせも兼ねている.

設計時間:4h
印刷時間:1h
材料費:100円程度

・基板スタンド

f:id:cosmo-blog-admin:20200525184959j:plain f:id:cosmo-blog-admin:20200525185009j:plain

展示会で基板を斜めに置くためのスタンド.
部品とぶつからないようにAltiumで形を決めてから3DCADで製作した.
斜めの部分も45°に納めているのでスペーサーをプリントせずに作れた.
基板がずり落ちてしまうとICに負荷がかかるので穴径はタイトにした.
内部密度20%.

設計時間:4h
印刷時間:24h
材料費:1500円程度

UARTを使ってSDカードを書き換える

 装置の蓋を開けずにデザインを交換したいという要求があった。該当の装置は外部へのデバッグ用I/FがUSBを使ったUARTのみしかなく、UART経由でデザインを更新することを考える。

lrzを使用することで、TeratermのZMODEというものを使用してファイルを送信し、SDカードを更新できる。

方法

lrzコマンド

通常、petalinuxでは、

# petalinux-config -c rootfs

して、「Filesystem Package」->「console」->「network」の「lrzsz」を有効にすると、lrzコマンドが使用可能になる。 容易にSDカードが取り出せない状態で、何回も更新する必要があるような場合、便利なのかもしれない。(ただし速度が遅く11KB/sec)

*参考 http://aster-ism.hatenablog.com/entry/2017/07/11/223550

今回はこのlrzszを有効にしていない状態なので、力技でlrzコマンドを使えるようにし、ファイル転送を行う。lrzコマンドをテキストエディタでバイナリモードでASCIIデータとして開く。このASCIIデータをechoコマンドを使ってTeratermから転送し、lrzコマンドを作る。

f:id:cosmo-blog-admin:20200423152816p:plain
lrzコマンドのバイナリデータ(lrz_bin_echo.txt)

lrz_bin_echo.txtを送る際に、一度に70行ぐらいを転送しようとすると、Teratermの制限か何かで、途中までしか転送されないことがあるので、30行づつぐらいに刻んでコピペをするとよい。(1行に何文字あるか、にも依存すると思う。)

*参考 https://nagayasu-shinya.com/bash-echo-binary/

TeratermのZMODEを使用してファイル転送

あとはlrzコマンドを叩き、

# chmod 777 /path/to/lrz
# /path/to/lrz
lrz waiting to receive.**B0100000023be50

となるのでTeraterm上の「ファイル」->「転送」->「ZMODEM」->「送信」でファイル選び、転送する。

※ちなみに
前述したように転送速度が11KB/secとなり、今回転送したかったBOOT.BIN(約17MB)を転送するのに30分ぐらいかかる。

AXI Chip2Chipを使用した系のデバッグと対応

前置き

 Zynqにぶら下がっている2台のFPGAレジスタ設定用にAXI Chip2Chip IPを使用しようとしたところ、IPが期待した通りに動かなかったためそのデバッグと対策を行った。

現象

 下記系で設計し実機デバッグを行ったところ、SoC-FPGA1間はLinkが立つが、FPGA1-FPGA2間のIPがLinkもErrorも立たない現象が発生した。

f:id:cosmo-blog-admin:20200413132407p:plain
当初想定していた系

IPの外部I/FはSelectIO DDR(Compact 4-1)、AXIバスの接続はAXI Interconnectを使用している。 なお1台のFPGAにAXI Chip2chip IPを2つ置いてインプリを行うとエラーが発生するため、Xilinxのフォーラムなどを参考にIPのMaster/SlaveでIDELAYCTRLとIDELAYE3をグループ分けしている。

調査内容

問題の調査に当たって下記を確認した。

  • XilinxのフォーラムやAnswer Recordなどで同様の症状が報告されていないか確認。→なかった。
  • 社内実績のあるプロジェクトでFPGAがUltrascaleではなく7シリーズの場合と、Ultrascaleを使用しているが外部I/Fにトランシーバーを使用している場合は同様の問題が発生していなかった。→今回の系特有の問題?
  • IP間の信号の接続方法を疑い、シミュレーションをやり直したがそれは問題なかった。→信号の接続の仕方が悪いわけではなさそう。
  • FPGA間の信号線が物理的に途切れていることを疑ったが、使用しているIOで別途テスト信号を送受信すると正常にできた。→物理的な信号は問題ない。
  • IPの動作モードがCommon/Independentと二つ存在するため、両方試してみたが、どちらでもリンクしなかった。
  • IP用のクロックとリセットの仕方の組み合わせをいろいろ変更してみたがどの場合もリンクしなかった。
  • IPのバグを疑い、インプリに使用するVivadoのバージョンやAXI Chip2chip IPのバージョンを変更してみたが状況は同じであった。
  • IDELAYCTRLとIDELAYE3をグループ分けしないとインプリが通らない事やSoC-FPGA1間はリンクしていることから、1つのFPGAにIPが1つなら問題ないのかと考え、試しにSoC-FPGA1間のIPを削除してみたらFPGA1-FPGA2間のIPがリンクした→IPが複数あることが原因?
  • IPからはクロックとデータが入出力されるが、クロックは問題なく出ている。ただしIP起動時のリンク用のデータ出力が途中で止まってしまっている。→IP内部の起動シーケンスがおかしくなっていそう。

 フォーラムなどを見ると「FPGAが7シリーズで外部I/FがSelectIO」か「FPGAがUltrascaleで外部I/Fがトランシーバー」のケースしか見かけられなかったため、「FPGAがUltrascaleで外部I/FがSelectIO」という組み合わせで発生するバグか、もしくはその組み合わせの際に特別に何か設定する必要があるかのどちらかが原因と思われるが、情報が少なくまたIP内部のシーケンスの問題の場合は対処の方法がないためこれ以上のデバッグは断念した。

行った対応

 SoC-FPGA1間の動作は問題なかったことと、FPGA1-FPGA2間の接続はレジスタの設定/読み出しのみで遅延をあまり考える必要がなく複雑な処理も必要なかったため、FPGA1-FPGA2間のみ外部I/Fを自作のシリアル通信I/Fに変更して対処した。

f:id:cosmo-blog-admin:20200413145711p:plain
最終的な系

 今回の系では外部I/FにSelectIOを使用していたが、トランシーバーを使用した方がFPGA間の信号の接続本数が少なくて済みいろいろと考える必要が少ないため、今後同じ問題に当たる可能性は低いかもしれないが参考のため記録に残しておく。