lp6m’s blog

いろいろかきます

Ubuntu on ZYBO Z7-20からFPGAで画像処理したPCam 5Cの映像をV4L2デバイスの映像として取得したい(失敗)

タイトルが随分ながくなってしまった。
前回Ubuntu on ZYBO Z7-20からPCam 5Cの映像を取得したい(成功) - lp6m’s blogでは、PCam 5CカメラをV4L2デバイスとして認識させ、画像を取得することができた。
せっかくMIPI経由でFPGA側に画像の信号があるので、HLSコアを用いて画像処理することはできないかと思った。
FPGAで画像処理しているカメラの画像がV4L2デバイスとしてOSに認識されて、簡単に画像取得できるとたぶんかなり嬉しい。
途中まで成功して、途中からは失敗したのでとりあえずログをまとめる。

やりたいこと

やりたいことの概略図は以下のようになる。
f:id:lp6m:20181022155350j:plain
元々のプロジェクトのFrame Buffer Write IPをVDMAに差し替えて、HLSコアを挿入するだけ。
元々のプロジェクトではYUYV形式でAXI4-StreamのDataWidthは16bit。
とりあえず情報を8bitに落とすHLSコアをつくって、1画素あたり8bitの情報を転送したい。

元のVivadoプロジェクトの確認

※元プロジェクトにはMIPI CSI2RX Subsystem IPが含まれており、LogiCore IPライセンスがないと合成やビットストリーム出力をすることができません。

1. DigilentgithubリポジトリGitHub - Digilent/Zybo-Z7-20-base-linuxからgit cloneする。

repo/vivado-libraryがgit submoduleになっている。回路で使用されているコアが別リポジトリに含まれており必要なので、以下コマンドでサブモジュールごとcloneする

git clone --recursive https://github.com/Digilent/Zybo-Z7-20-base-linux

Tools->Report->Report IP StatusからIP情報を更新、合成を行ってビットストリーム生成に成功することを確認。

Frame Buffer Write IPをVDMA IPに差し替え(ここまでは成功)

Vivado側の作業

元の回路にはFrame Buffer Write IPとよばれるIPが使われている。決まったビデオフォーマットにのSS2M(AXI4-Stream -> Memory Mapped)書き込みに特化したIPらしい。
Frame Buffer Write IPのフォーマットを変更してもDataWidthが8bitのフォーマットはなかったので、これをVDMAに差し替えて好きなデータ幅の転送ができるようにする。
とりあえずHLSコアを入れずに、VDMAに差し替えるだけを行う。
f:id:lp6m:20181022161304p:plain
axis_subset_converter_0とv_frmbuf_wr_0を削除して、VDMAを挿入
f:id:lp6m:20181022162054p:plain
この状態で合成・ビットストリーム生成を行う。
Export Hardwareを行い、Petalinuxプロジェクトにコピー。

Petalinuxプロジェクト側の作業

petalinux-config --get-hw-description <.hdfのあるディレクトリ>

HW情報をpetalinuxに読みこませる。
次にデバイスツリー(system_user.dtsi)を書き換える。FrameBuffer Write IPに対応する部分をコメントアウトして、amba_pl/video_capで指定しているDMAをFrameBuffer Write IPからVDMA IPに変更。
オリジナルとのdiffを載せておく。

10c10
< 		bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio root=/dev/mmcblk0p2 rw rootwait";
---
> 		bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
264c264
< /*&v_frmbuf_wr_0 {
---
> &v_frmbuf_wr_0 {
273c273
< };*/
---
> };
278c278
< 		dmas = <&axi_vdma_2 0>;
---
> 		dmas = <&v_frmbuf_wr_0 0>;
335,336d334
< 
<

これでPetalinuxのプロジェクトをビルドする。

petalinux-build

ここまでやってpetalinux-buildすると以前作成したカメラ画像取得プログラムが正常に動作した。

HLSコアの作成

YUYV16bitでは、1画素の情報は「YとU」あるいは「YとV」である(UとVは2画素に1つしか情報がなく、軽量)。いずれの場合もはじめの8bitを取り出す、すなわちYの値だけをとりだすコアを作成する。
Vivado HLSで1画素ずつの処理を行うサンプルが以下で公開されているので、それを参考にさせていただく。
github.com
yuyvyというモジュールを作成した。また、axiliteプロトコルしきい値を設定するといったことも将来的に行いたいので、char型のしきい値を入力として受けるようにした。
作成したコアのコードは以下リンク。
Extract Y 8bit data from YUYV 16bit data. · GitHub
実際に計算を行っているのは59行目の以下の部分だけ。YUYVからYの値を取り出して、valとのmaxを結果とする。特に意味はない。
コードの大部分はTLASTやTUSERなどの信号を扱うためのコード。解説されると簡単だけと自分で書くのは難しい・・

axis_writer.data = std::max((int)((axis_reader.data & 0xFF00) >> 8), (int)val);

高位合成の結果は以下の通りになった。まだ未熟で見方がわかってないが、特に問題はないらしい。。?
f:id:lp6m:20181022164438p:plain
Export RTLでIPとしてエクスポートを行った。

HLSコアを回路に挿入

Vivadoプロジェクトに戻り、IP CatalogにエクスポートしたHLS IPコアを追加する。
回路にHLS IPコアを挿入。
f:id:lp6m:20181022165240p:plain
HLSコアはs_axiliteプロトコルの入力があるので、メモリマップする必要がある。Address Editorでアドレスを割り当てる。
とりあえず、0x43c80000から64K割り当てた。
f:id:lp6m:20181022174510p:plain
この状態で合成・ビットストリーム生成を行う。
Export Hardwareを行い、Petalinuxプロジェクトにコピー。

Petalinuxプロジェクトの編集

先ほどと同様にpetalinux-config --get-hw-description <ディレクトリ>で新しい回路情報をプロジェクトにロードする。
ここから、Petalinuxプロジェクト側でデバイスツリーを書き換える。

Digilent Linux Kernelの話

注意しなければならないことが1つある。PetalinuxではLinuxカーネルの参照をgitリポジトリにすることができるのだが、
元のPetalinuxプロジェクトはDigilent Linux Kernelリポジトリの最新版ではなく、途中のコミットのものを参照している。
これを最新のものを参照するようにpetalinux-configで書き換えてしまうと動かなかった。
例えばMIPI CSI2RX Subsystemのデバドラは最新版使用しているPetalinuxが参照しているバージョンを見比べてみる。 旧版ではデバイスツリーのreset-gpioプロパティで指定したピンのリセットをxcsi2rxss_start_stream関数で行うが、最新版では行わない。
サンプルプロジェクトではMIPI CSI2RXSSのvideo_aresetnピンはGPIOに接続されており、これがリセットされないことでMIPI CSI2RXSSのSoft Resetがタイムアウトしてしまう。
(最新版のデバドラを使う場合は他の方法でvideo_aresetnをリセットしないといけなくなった)

まあ要は元々のままにしておけばいいのだけど、自分でカーネルをフォークする場合などは要注意。これで3日溶かした。

V4L2デバイスの話

HLSコアをV4L2のサブデバイスとして登録してくれるデバイスドライバxilinx-hls.cがDigilent Linux Kernelには存在する。
ソースコード
linux-digilent/xilinx-hls.c at 1496c680c6df2e3911feed13aa9663a851bf30e9 · Digilent/linux-digilent · GitHub
ドキュメント:
linux-digilent/xlnx,v-hls.txt at 1496c680c6df2e3911feed13aa9663a851bf30e9 · Digilent/linux-digilent · GitHub
これらを参考にして、デバイスツリーを編集する。

はじめの「やりたいこと」でも表している図の説明になるが、カメラ+画像処理がV4L2デバイス/dev/video0として認識されるのは、大体以下のような仕組み(のはずと理解したつもり・・・)

  • video_capが「xlnx,video」ドライバによって/dev/video0として認識される
  • xlnx,videoドライバはデバイスツリーで指定されている「エンドポイント」を辿って、サブデバイスを探す
  • PCam 5C、HLS IP、MIPI CSI2RX IPは各デバイスドライバによってV4L2サブデバイスとして登録される

 (各デバイスドライバは必要に応じてIPコアのリセットなどを適切に行ってくれる。ユーザアプリケーションはioctl()サービスコールを用いてドライバを呼び出すことができる)

バイスツリーの編集

バイスツリーの編集
そのまえに、Petalinuxで自動的に生成されるデバイスツリーをみる。(これはおそらくXilinx SDKで自動生成できるものと同じ)
petalinux-config --get-hw-descriptionでは回路に対応するデバイスツリーはなぜか生成されない。
とりあえずエラーがでてもいいので、petalinux-buildコマンドを叩くと回路に対応するデバイスツリーが更新される。
生成されるデバイスツリーは/components/plnx_workspace/device-tree/device-tree-generation/にある。pl.dtsiが回路に対応するデバイスツリー。
自動生成されたpl.dtsiを参考程度に貼っておく。
Dropbox - File Deleted
ユーザが記述するデバイスツリーsystem_user.dtsiはこの自動生成されたデバイスツリーに情報を付加・補完する形になっている。
それぞれのサブデバイスの接続情報がデバイスツリーにかかれているので、これらを編集する。
編集したデバイスツリーは以下。
Dropbox - File Deleted
オリジナルのデバイスツリーとのdiffは以下の通り。

245c245
< 				remote-endpoint = <&hls0_in>;
---
> 				remote-endpoint = <&vcap_in>;
264c264
< /*&v_frmbuf_wr_0 {
---
> &v_frmbuf_wr_0 {
273c273
< };*/
---
> };
278c278
< 		dmas = <&axi_vdma_2 0>;
---
> 		dmas = <&v_frmbuf_wr_0 0>;
289,311d288
< 					remote-endpoint = <&hls0_out>;
< 				};
< 			};
< 		};
< 	};
< };
< 
< yuyvy_0: yuyvy@43c80000 {
< 		compatible = "xlnx,v-hls";
< 		reg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>;
< 		clocks = <&clkc 15>;
< 
< 		ports {
< 			#address-cells = <1>;
< 			#size-cells = <0>;
< 
< 			port@0 {
< 				reg = <0>;
< 
< 				xlnx,video-format = <XVIP_VF_YUV_422>;
< 				xlnx,video-width = <8>;
< 
< 				hls0_in: endpoint {
315,324d291
< 			port@1 {
< 				reg = <1>;
< 
< 				xlnx,video-format = <XVIP_VF_MONO_SENSOR>;
< 				xlnx,video-width = <8>;
< 
< 				hls0_out: endpoint {
< 					remote-endpoint = <&vcap_in>;
< 				};
< 			};
325a293
> 	};
367,368d334
< 
< 

HLS IPが1画素8bitのデータを出力する。これに相当するvideo-formatの値はXVIP_VF_MONO_SENSORのようだ。(UG934, 9ページ参照)

yuyvy_0: yuyvy@43c80000内のreg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>の意味がわからない。
ドキュメントによると

  • reg: Physical base address and length of the registers sets for the device.

The HLS core has two registers sets, the first one contains the core
standard registers and the second one contains the custom user registers.

らしいけど、standard registerとcustom user registerってなんのことだろう・・?とりあえずデフォルトの0x0024の範囲をstandard registerにしたけど。
HLS IPには64K割り当てたのでとりあえず合計0x100000になるようにした。

カーネルコンフィグの変更

元々のpetalinuxプロジェクトではHLS IPに対応するデバイスドライバが有効になっていない。

petalinux-config -c kernel

カーネルコンフィグを表示して、xlnx,hlsを有効(Build-In)にする。
f:id:lp6m:20181022175621p:plain

これでPetalinux側の作業も終わり。プロジェクトのビルドおよびFSBLとU-BOOTのビルドを行う。

petalinux-build 
petalinux-build -c fsbl
petalinux-build -c u-boot

BOOT.BINの作成

以下コマンドでBOOT.BINを作成する。

petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga <bitファイルのパス> --u-boot --force

起動およびデバイス認識の確認

これまでどおりSDカードのFATでフォーマットした第1パーティションにimage.ubとBOOT.BINを,第2パーティションUbuntuのrootfsを入れる。
PCam 5Cを接続し、/dev/media0と/dev/video0が見えていればとりあえずデバイスドライバが正常にデバイスを登録できていることが確認できる。。

Ubuntuにはsudo apt-get install v4l-utilsでV4L-Utilsをインストールしておく。
ログイン後、sudo su -コマンドでスーパユーザに切り替えてから、V4L2サブデバイスのフォーマットの設定を行う。(本家と同じコマンド)

root@arm:~#
width=1920
height=1080
rate=15
media-ctl -d /dev/media0 -V '"ov5640 2-003c":0 [fmt:UYVY/'"$width"x"$height"'@1/'"$rate"' field:none]'
media-ctl -d /dev/media0 -V '"43c60000.mipi_csi2_rx_subsystem":0 [fmt:UYVY/'"$width"x"$height"' field:none]'

このコマンドで、デバイスツリーでフォーマットを指定していていなかった情報をセットしている。
V4L2デバイスが正しく認識、かつ正しくパラメータがセットされていることを以下のコマンドで確認する。

root@arm:~# media-ctl -p /dev/video0
Media controller API version 4.9.0

Media device information
------------------------
driver          xilinx-video
model           Xilinx Video Composite Device
serial          
bus info        
hw revision     0x0
driver version  4.9.0

Device topology
- entity 1: video_cap output 0 (1 pad, 1 link)
            type Node subtype V4L flags 0
            device node name /dev/video0
	pad0: Sink
		<- "43c80000.yuyvy":1 [ENABLED]

- entity 5: ov5640 2-003c (1 pad, 1 link)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev0
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "43c60000.mipi_csi2_rx_subsystem":1 [ENABLED]

- entity 7: 43c80000.yuyvy (2 pads, 2 links)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev1
	pad0: Sink
		[fmt:UYVY/1920x1080 field:none]
		<- "43c60000.mipi_csi2_rx_subsystem":0 [ENABLED]
	pad1: Source
		[fmt:Y8/1920x1080 field:none]
		-> "video_cap output 0":0 [ENABLED]

- entity 10: 43c60000.mipi_csi2_rx_subsystem (2 pads, 2 links)
             type V4L2 subdev subtype Unknown flags 0
             device node name /dev/v4l-subdev2
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "43c80000.yuyvy":0 [ENABLED]
	pad1: Sink
		[fmt:UYVY/1920x1080 field:none]
		<- "ov5640 2-003c":0 [ENABLED]

登録は成功しているようだ。特にHLS IPである43c80000.yuyvyのSourceのフォーマットは[fmt:Y8]となっており、1画素のデータが8bitのデータが出力されるということが登録されている。

カメラ撮影プログラムの修正

カメラ撮影プログラムは以前作成したプログラムを元に作成する。
HLSコアにはs_axilite経由の入力値があるので、これをセットする必要がある。HLSコアは先ほどのコマンドで/dev/v4l-subdev1に認識されていることが確認できた。
VIvadoHLSでExport RTLを行うと、IPコアのためのドライバが出力される。(/solution1/impl/ip/drivers/yuyv_v1_0/src)
この中にあるxyuyv_hw.hは以下の通り。

// CONTROL_BUS
// 0x00 : reserved
// 0x04 : reserved
// 0x08 : reserved
// 0x0c : reserved
// 0x10 : Data signal of val_r
//        bit 7~0 - val_r[7:0] (Read/Write)
//        others  - reserved
// 0x14 : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)

#define XYUYVY_CONTROL_BUS_ADDR_VAL_R_DATA 0x10
#define XYUYVY_CONTROL_BUS_BITS_VAL_R_DATA 8

ここから、物理アドレスの0x43c80000+オフセット0x10の位置に値を書き込めば、HLSコアの入力値valを設定できるということがわかる。
嬉しいことに、HLS IPのデバドラにはs_axilite経由の値をサービスコールを用いて書き込む機能がある。
xilinx-hls-common.hやxilinx-vip.hを含めてデバイスドライバソースコードを読んだところ。

struct buffer_addr_struct{
  void *start[FMT_NUM_PLANES];
  size_t length[FMT_NUM_PLANES];
} *buffers;


struct xilinx_axi_hls_register {
  __u32 offset;
  __u32 value;
};

struct xilinx_axi_hls_registers {
  __u32 num_regs;
  struct xilinx_axi_hls_register *regs;
};

#define XILINX_AXI_HLS_READ _IOWR('V', BASE_VIDIOC_PRIVATE+0, struct xilinx_axi_hls_registers)
#define XILINX_AXI_HLS_WRITE _IOW('V', BASE_VIDIOC_PRIVATE+1, struct xilinx_axi_hls_registers)

を記述しておき、

xilinx_axi_hls_register reg[1];
reg[0].offset = 0x10;
reg[0].value = 255;
xilinx_axi_hls_registers regs;
regs.num_regs = 1;
regs.regs = reg;
int fd2;
fd2 = open("/dev/v4l-subdev1", O_RDWR, 0);
if (fd2 == -1){
  std::cout << "Failed to open subvideo device." << std::endl;
  return 1;
}
if (-1 == xioctl(fd2, XILINX_AXI_HLS_WRITE, &regs)){
  std::cout << "Failed to set param via s_axilite" << std::endl;
  return 1;
}

を実行すれば値を書き込めそうだということがわかった。reg[0].offset = 0x10;の値は先程のxyuyv_hw.hを参考に設定した。

また、/dev/video0に設定するフォーマットは1画素8bitのフォーマットなので、

fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_GREY;

に変更する。

最終的に完成したプログラムのコードを以下に示す。
Dropbox - File Deleted

実行・・失敗

実行してみたが、途中で止まってしまった。実行ログは以下の通り。

root@arm:/home/ubuntu# ./a.out
waiting in xioctl() 
waiting in xioctl() 
bus_info	: platform:video_cap:0
card		: video_cap output 0
driver	: xilinx-vipp
version	: 264448
waiting in xioctl() 
waiting in xioctl() 
reqbuf.count : 3
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb6a9a000
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb689f000
waiting in xioctl() 
buf.length : 1
buf.m.offset : 3196044192
buf.m.planes[j].length : 2073600
buffers[i].start[j] : 0xb66a4000
waiting in xioctl() 
waiting in xioctl()

具体的にはxioctl(fd, VIDIOC_STREAMON, &buf.type))を実行してカメラのキャプチャをスタートしようとすると、サービスコール呼び出しの実行がおわらず、待機状態になる。
バッファの確保はできているようだ。

わからないこと

  • なぜ取得できなかったのか・・

 いろいろ理由を考えているけど、わからない。

  • 前述のyuyvy_0: yuyvy@43c80000内のreg = <0x43c80000 0x0024>, <0x43c80024 0xFFDC>はどのように設定するのが正しいのか

この設定とカメラ取得プログラムのreg[0].offset = 0x10;は関係ありそう。デバイスドライバをもう少しきちんと読もう。

  • HLSコアはap_ctrl_noneになっているが、axilite経由の入力の値がセットされていない場合はどのように動作するのか?

デフォルト値(intなら0とか)が使用されるのかなと思っている。入力の値がセットされていないから計算が実行されない、なんていうことは多分ないと思っている。
(もしそうなら、カメラ取得プログラムで正しく入力値valが設定されていないせいでHLSの出力がでていない、というのも原因として考えられる。)
そうでないなら、axilite経由の値をうまくセットできていなかったとしても何らかの出力は出るのではないか・・?

  • Vivado上で配線にMark as Debugを設定し、ILAコアをいれた場合Linuxを動作したままHardware Manager上でILAデバッグができるのか?

これに関してはTwitter上でできるという話を聞いたが、やってみたが上手くいかない・・

  • タイミングの問題が原因?

これは見方をまだ理解していない。赤字なので何かヤバそう・間に合っていないとは思う。ただ、回路を変更する前から赤字はあった。値の変化はまだ確認していない。
f:id:lp6m:20181022215459p:plain


ブログが非常に長くなってしまった。何か原因として考えられることがあればご教示願います・・・

Ubuntu on ZYBO Z7-20からPCam 5Cの映像を取得したい(成功)

前回Ubuntu on ZYBO Z7-20からPCam 5Cの映像を取得したい(失敗) - lp6m’s blogの続き。成功したのでまとめておく。

やったこと

前回なぜかyavtaコマンドで画像の取得ができなかった。
Xilinx Forumに質問した。
https://forums.xilinx.com/t5/Embedded-Linux/can-t-get-image-from-PCam-5C-on-Ubuntu-running-on-ZYBO-Z7-20/m-p/882879#M28057

どうやら以下のコマンドでは画像の取得ができた。なぜyavtaだとダメなのかは不明。

v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV --stream-mmap --stream-count=1 --stream-to=test.raw

とりあえずこれで取得できるので、v4l2-ctlコマンドの実装を参考にC++コードを記述する。
コードは以下。
Get image from PCam 5C on Ubuntu running on ZYBO-Z7-20 · GitHub

Ubuntu on ZYBOでapt-get upgrade, apt-get update, apt-get install g++ gccしておく。
特に外部ライブラリを使用しないのでg++ cam.ccでビルドできる。PcamはUbuntu起動前に接続しておく。

以下のおまじないコマンドを叩く

sudo media-ctl -d /dev/media0 -V '"ov5640 2-003c":0 [fmt:UYVY/'1920x1080'@1/'15' field:none]'
sudo media-ctl -d /dev/media0 -V '"43c60000.mipi_csi2_rx_subsystem":0 [fmt:UYVY/'1920x1080' field:none]'
$ ./a.out
bus_info	: platform:video_cap:0
card		: video_cap output 0
driver	: xilinx-vipp
version	: 264448
reqbuf.count : 3
buf.length : 1
buf.m.offset : 3195409312
buf.m.planes[j].length : 4147200
buffers[i].start[j] : 0xb687e000
buf.length : 1
buf.m.offset : 3195409312
buf.m.planes[j].length : 4147200
buffers[i].start[j] : 0xb6489000
buf.length : 1
buf.m.offset : 3195409312
buf.m.planes[j].length : 4147200
buffers[i].start[j] : 0xb6094000
r : 0
store image in array....
write data...

画像が取得できた!
得られたYUYV形式のcamdata.datファイルは以下のPythonコードでpng画像に変換できる。
Convert Raw YUYV image from PCam 5C to png · GitHub

python test.py camdata.dat out.png

Pythonコード内のRGBへの変換が間違っているらしく、緑っぽい画像が出力されるかも・・)

勉強したこと

  • 上に書いたおまじないコマンドは何なのか

これは、V4L2 SubDeviceそれぞれに使用するフォーマットを教えている。SubDeviceに設定されたフォーマットは以下のコマンドで確認できる。

$media-ctl /dev/video0 -p
Media controller API version 4.9.0

Media device information
------------------------
driver          xilinx-video
model           Xilinx Video Composite Device
serial          
bus info        
hw revision     0x0
driver version  4.9.0

Device topology
- entity 1: video_cap output 0 (1 pad, 1 link)
            type Node subtype V4L flags 0
            device node name /dev/video0
	pad0: Sink
		<- "43c60000.mipi_csi2_rx_subsystem":0 [ENABLED]

- entity 5: ov5640 2-003c (1 pad, 1 link)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev0
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "43c60000.mipi_csi2_rx_subsystem":1 [ENABLED]

- entity 7: 43c60000.mipi_csi2_rx_subsystem (2 pads, 2 links)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev1
	pad0: Source
		[fmt:UYVY/1920x1080 field:none]
		-> "video_cap output 0":0 [ENABLED]
	pad1: Sink
		[fmt:UYVY/1920x1080 field:none]
		<- "ov5640 2-003c":0 [ENABLED]

V4L2 SubDevice参考:https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dev-subdev.html
きちんとPCam(OV5640) -> MIPI_CSI2_RX_SubSystem -> video_capにデータが流れるようになっていることがわかる。
この接続はPetaLinuxプロジェクトのデバイスツリー(project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi)内でremote-endpointで指定されている。
以下、system-user.dtsi一部抜粋

&axi_iic_0 {
	...
	ov5640: camera@3c {
		compatible = "ovti,ov5640";
		...
		port {
			ov5640_to_mipi_csi2: endpoint {
				remote-endpoint = <&mipi_csi2_from_ov5640>;
				...
			};
		};
	};
};

&amba_pl {
	video_cap {
		compatible = "xlnx,video";
		dmas = <&v_frmbuf_wr_0 0>;
		...
		ports {
			...
			port@0 {
				...
				vcap_in: endpoint {
					remote-endpoint = <&mipi_csi2_out>;
				};
			};
		};
	};
};


&mipi_csi2_rx_subsystem_0 {
	compatible = "xlnx,mipi-csi2-rx-subsystem-2.0";
	...
	ports {
		...

		port@0 {
			reg = <0>;
			xlnx,video-format = <XVIP_VF_YUV_422>;
			xlnx,video-width = <8>;
			mipi_csi2_out: endpoint {
				remote-endpoint = <&vcap_in>;
			};
		};
		port@1 {
			reg = <1>;
			xlnx,video-format = <XVIP_VF_YUV_422>;
			xlnx,video-width = <8>;
			mipi_csi2_from_ov5640: endpoint {
				...
				remote-endpoint = <&ov5640_to_mipi_csi2>;
			};

		};

	};
};	

これができたのが8月末。
第8回 相磯秀夫杯 FPGAデザインコンテストではこれを使用した。
FPGAでは何の画像処理も行っていない状態で、ソフトウェアに頑張らせた。

次はFrame Buffer Write IPをVDMAに置き換えて、HLSコアを挿入する。
VDMAに置き換えるところまでは昨日できた。
ブログが書くのが下手で、わかってる人しかわからない記事になってしまった気がするけど。。

Ubuntu on ZYBO Z7-20からPCam 5Cの映像を取得したい(失敗)

Digilent社製ZYBO Z7-20でUbuntuを動作させ、PCamの映像を取得したい。
store.digilentinc.com
store.digilentinc.com

git cloneしてサンプルを試す:成功

Petalinuxツールを使用してZYBO上で動作するLinuxカーネル・rootfsを作成することができる。
Digilentgithubにサンプルがあるのでこれをcloneする。
github.com

デフォルトではRAMにrootfsを展開する設定(initramfs)になっているので、githubのREADMEのBoot the newly built files from SDに書いてあるとおりに、SD Bootに変更・system-user.dtsi のbootargsを変更した。

第一パーティションカーネルとイメージ(BOOT.BIN・image.ub)をコピー
第二パーティションにROOTFSをコピー(images/linus/rootfs.tar.gzを展開)

起動確認、githubのREADMEに書いてあるとおりのコマンドでPCamからの映像を取得成功

width=1920
height=1080
rate=15
media-ctl -d /dev/media0 -V '"ov5640 2-003c":0 [fmt:UYVY/'"$width"x"$height"'@1/'"$rate"' field:none]'
media-ctl -d /dev/media0 -V '"43c60000.mipi_csi2_rx_subsystem":0 [fmt:UYVY/'"$width"x"$height"' field:none]'
v4l2-ctl -d /dev/video0 --set-fmt-video=width="$width",height="$height",pixelformat='YUYV'
yavta -c14 -f YUYV -s "$width"x"$height" -F /dev/video0

rootfsをUbuntuに変更

Petalinuxでビルドしたrootfsではapt-getなどのパッケージ管理システムを使用することができない。
ARM用にビルドされたUbuntuのrootfsをダウンロードしてarmhf-rootfs-ubuntu-xenial.tar を第二パーティションに展開
参考;ZYBO-Z7を用いたLチカ(Ubuntu16.04編) - aster_ismの工作室

wget https://rcn-ee.com/rootfs/eewiki/minfs/ubuntu-16.04.3-minimal-armhf-2017-10-07.tar.xz

Ubuntuの起動確認・必要なソフトウェアのインストール

Ubuntuの起動を確認。

v4l-utilsをインストール

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install v4l-utils

※電源が不安定になりapt-get upgrade中にボードが再起動することがあるので、補助電源をボードに接続する・電流が大きいものを使用するなどして対処

yavtaのインストール・yavtaはapt-getでは入らない

sudo apt-get install build-essential
git clone https://github.com/fastr/yavta
cd yavta
make

*画像取得(失敗)
sudoをつけた以下のコマンドを実行

width=1920
height=1080
rate=15
sudo media-ctl -d /dev/media0 -V '"ov5640 2-003c":0 [fmt:UYVY/'"$width"x"$height"'@1/'"$rate"' field:none]'
sudo media-ctl -d /dev/media0 -V '"43c60000.mipi_csi2_rx_subsystem":0 [fmt:UYVY/'"$width"x"$height"' field:none]'
v4l2-ctl -d /dev/video0 --set-fmt-video=width="$width",height="$height",pixelformat='YUYV'
./yavta/yavta -c14 -f YUYV -s "$width"x"$height" -F /dev/video0

yavtaを実行するとエラーが起きる。

Error opening device /dev/video0: neither video capture nor video output supported.

yavtaの実装を見て調べたところ、 yavta.cの121行目でioctl()を使ってデバイスの属性を取得した際にカメラの属性が取得できていない

原因究明

必要なドライバがロードされていないのでは?と思いdmesgやls /sys/moduleの結果を比較したが特に理由は見つからない・・・

また、petalinux-config -c kernelによるカーネルの設定でXilinx Video IPのドライバを、カーネルにビルトインする設定からモジュールに変更してみた。
ビルドしたpetalinuxのrootfsの/libフォルダ内に読み込むべきドライバが生成されるので、それらをubuntuの/libにコピーしてみた。しかし、同様にエラーでカメラの映像を取得することができない・・・

dmesgおよびls /sys/moduleの結果をpetalinuxのときとUbuntuのとき両方を貼っておく。

  • 成功したほうのdmesg

petalinux_dmesg.log · GitHub

  • 失敗したほうのdmesg

ubuntu_dmesg.log · GitHub

  • 成功したほうのls /sys/module

petalinux_sys_module.log · GitHub

  • 失敗したほうのls /sys/module

ubuntu_sys_module.log · GitHub

AR# 50826 『ブロックRAM から OCM へのキャッシュ コヒーレントの CDMA 転送 』を試した

HW関連の研究室でZynq All Programmable SoCを触っています。
正直Xilinxのマニュアルは難しすぎてよくわからない・・・体系的に学べるといいのだけれど。

オンチップメモリ(OCM)への書き込みをテストするために調べていたら、以下のページが見つかった。
https://japan.xilinx.com/support/answers/50826.htmljapan.xilinx.com


今回はこれを試してみた。自分なりの解釈とかのメモを残す。

このアプリケーションは何か

このアプリケーションはBRAMの64箇所に0x01から0x40の値を、OCMの64箇所に0xCDの値をセットした後、CDMA(Central Direct Memory Access)を使用してBRAMからOCMへデータを送信する。送信後OCMの64箇所の値が0x01から0x40になっていれば成功。

CDMA

メモリマップされたアドレス間でデータの送受信を行うコントローラ。プロトコルはAXI4/AXI4-Liteが使用できる。

BRAM

FPGA上に配置することができるRAM。BRAM Generator IPがBRAM本体で、データの送受信はBRAM Controllerで動作させる。

OCM

256KBのオンチップメモリがPS上のAPU内に存在する。ACPポートは直接ここにつながっている?(よくわかっていない)

手順(いつものやつ。この本とかに詳しく書いています。)

1. プロジェクトをダウンロードしてVivadoで開いてビットストリーム生成
2. Export HardwareしてSDK(SDSoC)起動
3. File -> New -> Application Projectを選択、Hello Worldプロジェクトを作成し、添付されているhello_axi_cdma.cの内容をhelloworld.cにコピペ
4. Xilinx Toolsを開いてProgrammable FPGAを選択してビットストリーム書き込み
5. Build Allして実行用のelfファイル作成。右クリックしてRun As -> Launch On Hardware (System Debugger)を選択。
6. TeraTermなどから出力を確認(ポートレート115200に設定)

これで動けばよかったのだけど動かなかった。OCMの値全てが0xCDのままになっていた。

修正箇所

結論から言えば、サンプルソースの53行目を

Xil_SetTlbAttributes(0xFFF00000,0x14c0e);

から

Xil_SetTlbAttributes(0xFFF00000,0x14de2);

に修正。
 あるいは、Xil_SetTlbAttributes(0xFFF00000,0x14c0e);をコメントアウトし、
CDMAによる転送が終了した後の121行目などに、

Xil_DCacheInvalidateRange(dstCPU, 64*8);

を挿入。

Xil_SetTlbAttributesとは何か

色々調べてみると、TRM(テクニカルリファレンスマニュアル)UG585の73ページ図3-5にページエントリのフォーマットが書いてある。
またココによると図のSectionの欄が空欄になっているけどSuperSectionと同じらしい。
TEX[2:0], C, Bなどのビットが大切らしい。DomainとかXNとかnGは謎パラメタ・・・

Xil_SetTlbAttributesはキャッシュのポリシーなどを変更するための関数であるということがわかった。
0x14c0eは2進表記で0001_0100_1100_0000_1110 C=1 B=1 キャッシュ属性は「ライトバック、書き込み割り当てなし」
これだとキャッシュが有効になっているのでCDMAが成功していてもアプリケーションは古い値を参照してしまう。
0x14de2は2進表記で0001_0100_1101_1110_0010 C=0 B=0 キャッシュ属性は「キャッシュ不可」
キャッシュを切ることによってアプリケーションは正しい値を参照することができる。

CDMAやDMAを使用したとき、古い値を参照しないように気を付ける必要があるらしい。
DMAを使用したときは Xil_SetTlbAttributesなんて使っていなかったなと思って、Xil_DCacheInvalidateRangeを試したところ、これでもOKだった。
この関数だと、指定したアドレス範囲のデータに関してのみキャッシュが無効化され、新しい値が参照されるようになる。

その他

DMAを使用したときはVivadoでビットストリームを生成したときに出力されるドライバファイルxaxidma.h内で宣言される関数XAxiDma_SimpleTransferを使用してデータ送受信を行っていた。このサンプルアプリケーションにはそのような関数が含まれておらず、最初は意味がわからなかった。
CDMAのリファレンスを読んでみたところ解決した。
CDMAの制御レジスタを直接変更することでエラー割り込み、終了割り込みの有効化・送受信先アドレスの設定・データ転送開始を制御している。

また、ダウンロードしたサンプルのデザインを実行しても動かなかったので、手作業で回路を作り直した。
Vivado内でBRAMを配置した際にBRAMのサイズが変更できずに困っていたところ、以下のようなリンクを発見した。
https://forums.xilinx.com/t5/Design-Entry/how-to-edit-bram-controller-bram-memory-size/td-p/637284
BRAM GeneratorのWidthとDepthの欄はマスター側によって自動的に決まると書いてあった。
BRAMコントローラでBRAMのサイズを変更できないな~と思っていたら、Address EditorでBRAMコントローラに与えるアドレス範囲を変更することでBRAMのサイズが変更できる。

コード

Xilinxのサンプルコードを修正・コメント文を追加したものをおいておきます。
gist.github.com

分からないことも多いまま試行錯誤しているので、間違っているところなどあれば教えてください。

ICPC2017国内予選に参加しました

久々にブログを書いた
1年前:
lp6m.hatenablog.com

2年前:
lp6m.hatenablog.com

去年と同じメンバーで出た。チーム名はfoldLeft.
特に練習もしていないので自分は簡単な問題を解こうとおもっていた。
自分はABを通してチームメイトがCDを解いた。D解けるのすごい。
46位/368チームでした 2桁になれたのはチームメイトのおかげ。

勉強しないとな~

UbuntuやElementary OSでスリープ後に画面の明るさが変更できないのを解消できた

先日気が狂ってmacOSを消した.
UbuntuベースのElementary OSというOSをインストールした.
なんかスリープ後に画面の明るさが調節できなくてつらかったのでググりまくった.
いろんな情報がでてきたけどどれもダメでつらいなあとおもっていたら,今日はやっとみつけることができた.

まずココに同じ問題が書かれてる.
MacBookAir6-2/Trusty - Community Help Wiki

Mac用にドライバを書いてくれた人がいるみたい.インストールしようとした.READMEにしたがってすすめていく.
make installで以下のようなエラーがでて失敗する.

- SSL error:02001002:system library:fopen:No such file or directory: bss_file.c:175
- SSL error:2006D080:BIO routines:BIO_new_file:no such file: bss_file.c:178
sign-file: certs/signing_key.pem: No such file or directory

つらいと思ってしらべたら、同じのがあった.
github.com
make installできないときは,checkinstallってのを使えばいいらしい.
sudo apt install checkinstall
mba6x_blのディレクトリ内でcheckinstallのコマンドを叩く. なんかパッケージの説明をかけとか言われるので「Macのがめんの明るさ設定する」って書いた.
そしたらディレクトリ内にdebが作成された.checkinstallってのはdebを生成するコマンドらしい?
最後にsudo -i dpkg ~.debでインストール.
READMEに書いてる最後の手順のsudo modprobe mba6x_bl をしたらちゃんと反映された.

たぶんいまのところ動いてるっぽいのでよかった.インターネットすばらしいしMacのドライバかけるのすごい.

おわり.

Yahoo!ボックス からファイルを一括ダウンロードするスクリプト

Yahoo!ボックスとかいうオンラインストレージサービスがあるらしい。
info.box.yahoo.co.jp
アプリが終了してブラウザから1つ1つダウンロードするしかないらしい。助けてくれ!と友人に頼まれた。
しばらくChromeデベロッパーツールで眺めていると、ファイル情報をJSONでやりとりしているだけなようなので、書いた。

はじめはRuby の Mechanize を使って Yahoo! JAPAN にログインする - kaosf’s diaryを参考にしてログイン処理をしていたが、途中からログインできなくなった。Mechanizeだと今どのような状態なのかがわかりにくいので、Selenium-webdriverを使って再現してみると、文字認証を求められていた。
探してみるとrubyでYahoo Japanにログインする。Cookie発行してもらう - それマグで!のような情報がでてきたので、使わせていただいた。
(微妙にCAPCHAのURLの種類が違ったのでそのへんだけ書き換えたりした)
2016/12/30 Windows環境で動作させると,txt以外がファイルが壊れるとの指摘をいただきました。訂正しました。

#参考:http://takuya-1st.hatenablog.jp/entry/20121018/1350587902
#!/usr/bin/env ruby
#coding:utf-8
require 'rubygems'
require 'mechanize'
require 'open-uri'
require 'net/http'
require 'uri'

OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

Id       = 'XXXXXXXXXXXXXXXX'
Password = '****************'
cookie_jar_yaml_path = 'yahoo.yaml'
#Yahoo!ログイン
agent = Mechanize.new
agent.user_agent_alias = 'Windows IE 7'
agent.get('https://login.yahoo.co.jp/config/login?.src=www&.done=http://www.yahoo.co.jp')
agent.page.form_with(name: 'login_form') do |form|
	form.field_with(name: 'login').value = Id
	form.field_with(name: 'passwd').value = Password
	# agent.page.body =~ /\("\.albatross"\)\[0\]\.value = "(.*)"/
	# form.field_with(name: '.albatross').value = $1
	form.click_button
end

#CAPTHCA
str = agent.page.body.match( %r!"https://captcha.yahoo.co.jp:443/[^"]+!).to_s.gsub(/"/,"")
puts str
open(str) do |file|
  open("captcha#{Time.now.to_i}.jpg", "w+b") do |out|
    out.write(file.read)
  end
end
capthca = ''
$stdout.print 'enter captcha:'
captcha = $stdin.readline
puts "i got captcha#{captcha}"
agent.page.forms.first.fields_with(:type=>"text").first.value=captcha
agent.page.forms.first.submit

#CAPTHCA後の再ログイン
f=agent.page.forms[0]
f.fields_with( :name=>"login")[0].value=Id
f.fields_with( :name=>"passwd")[0].value=Password
f.submit


puts agent.page.body.to_s.toutf8

agent.cookie_jar.save_as(cookie_jar_yaml_path)
File.expand_path cookie_jar_yaml_path

これを実行すると、ログイン情報のクッキーがyahoo.yamlというファイルに出力される.

次に、Yahoo!ボックスからファイルを一括ダウンロードするスクリプトを実行する

#Yahoo! Box Downloader
require 'mechanize'
require 'nokogiri'
require 'kconv'
require 'scanf'
require 'date'
require 'uri'
require 'json'
require 'erb'
require 'net/http'
require 'open-uri'
include ERB::Util

cookie_jar_yaml_path = 'yahoo.yaml' #ログイン情報のクッキーを保存したファイル
filenum_of_page = 100 #一度に読み込むファイル数 20,50,100のどれか

#Yahoo!Boxへアクセス
agent = Mechanize.new
agent.user_agent_alias = 'Windows IE 7'
agent.cookie_jar.load(cookie_jar_yaml_path)
page = agent.get('https://box.yahoo.co.jp/user/viewer')	


#Javascriptの文字列からsid,uniqid,crumb,appidを取り出す
tmp_rst = page.search('script')[0]

user_parmsstr = tmp_rst.to_s.split("\n")[2].split(',')
crumb_parameter = tmp_rst.to_s.split("\n")[3].split(',')
appid_parameter = tmp_rst.to_s.split("\n")[4].split(',')

sid = user_parmsstr[0].scanf("    User  = {\'sid\':\"%s\"")[0].to_s
topuniqid = user_parmsstr[1].scanf(" \'uniqid\':\"%s\"},")[0].to_s
crumb = crumb_parameter[1].scanf("'bcrumb':\"%s")[0].to_s
appid = appid_parameter[0].scanf("\t\t'appid':\'%s")[0].to_s
puts appid
#scanfうまくいかないのでうしろの"を消す 正規表現ちゃんとかくべき^^;
sid = sid[0,topuniqid.index("\"",2)+1]
topuniqid = topuniqid[0,topuniqid.index("\"",2)]
crumb = crumb[0,crumb.index("\"",2)]
appid = appid[0,appid.index("'",2)]
puts "sid = #{sid}"
puts "uniqid = #{topuniqid}"
puts "crumb = #{crumb}"
puts "appid = #{appid}"

#ここから巡回してファイルをダウンロード	
folderList = Array.new
folderList.push(topuniqid)
#folderListが空になるまで巡回する
while folderList.size != 0 do
	#folderListから一つ取り出す
	nowuniqid = folderList.pop
	#そのフォルダ内のファイルのリストが書かれたJSONを取得する
	urlstr = "https://box.yahoo.co.jp/api/v1/filelist/" + sid + "/" + nowuniqid + "?_=" + DateTime.now.strftime('%Q').to_s + "&"
	urlstr << "results=#{filenum_of_page}&start=1&output=json&sort=%2Bname&filetype=both&meta=1&thumbnail=1&tree=1&sharemembercount=1&ownerinfo=1&boxcrumb="
	urlstr << url_encode(crumb)
	agent.get(urlstr)
	jsonstr = JSON.parse(agent.page.body.to_s)
	# 複数ページが存在する場合はまず全ページたどってファイル情報を入手
	filenum = jsonstr['ObjectList']['TotalResultsAvailable'].to_s
	unless jsonstr['ObjectList']['Object'] == nil
		jsonstr['ObjectList']['Object'].each do |object|
			type = object['Type'].to_s
			name = object['Name'].to_s
			uniqid = object['UniqId'].to_s
			dlurl = object['Url'].to_s
			path = "." + object['Path'].to_s #パスの先頭にドットをつけないとうまく相対パスにならない
			#ファイルかフォルダかで処理を分岐
			if(type == 'file') then
				dlurl << "?appid=#{appid}&error_redirect=1&done=https%3A%2F%2Fbox.yahoo.co.jp%2Ferror%2Fdownload_error&boxcrumb="
				dlurl << url_encode(crumb)
				#dlurlからリダイレクトされたURLを取得 これがダウンロードリンク
				agent.get(dlurl)
				redirect_link = agent.page.uri.to_s
				#ファイルを保存
				#File.write(path, Net::HTTP.get(URI.parse(redirect_link)))
				open(redirect_link) do |file|
					open(path, "w+b") do |out|
						out.write(file.read)
					end
				end
				puts "Download #{path}"
			elsif(type == 'dir') then
				#folderListに追加してあとで巡回
				folderList.push(uniqid)
				Dir.mkdir(path)
			end
		end
	end
end

詳しく解説してもたぶん需要なさそうなので、コードはりつけておしまい。
sleepをはさんで、負荷かけないようにしてつかいましょう。

一応gistこちらも修正しました[2016/12/30]

https://gist.github.com/lp6m/5913c1ef770f75825b00081a6ed7f671
https://gist.github.com/lp6m/a4e927963e218884e2a843e40e7a22b5