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を作成することができる。
Digilentのgithubにサンプルがあるのでこれを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
- 失敗したほうのdmesg
- 成功したほうのls /sys/module
petalinux_sys_module.log · GitHub
- 失敗したほうのls /sys/module
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になっていれば成功。
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
FPGAを買った。
計算機科学実験及演習3(ハードウェア)でFPGAを触った。パイプライン処理くらいはつくったけどバグがでてしまってソートコンテスト出れなかった。
FPGA面白いな~と思って実験でつかってるボードが何円するのか調べたら10万以上してびっくりした。
https://twitter.com/lp6m/status/755416445589164032
で、友達がもうちょっと7セグとかスイッチとか標準装備してるやつ買おうって言ったので便利そうだし買った。
https://twitter.com/lp6m/status/757829385953042432
試験が終わったら色々やってみたい。夏休みにどこまでいじれるかな。