zynq/Petalinux2018.3でaxi_gpio の履歴(No.12)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/zynq/Petalinux2018.3でaxi_gpio へ行く。
概要†
汎用 GPIO モジュールである axi_gpio を使い、Zynq の CPU から FPGA の入出力ピンを制御したい。
いわゆるLチカに相当するやつだ。
ここではキャラクタ LCD のリセットピンを制御したかった。
ロジックの作成†
ブロックダイアグラムに axi_gpio IP を追加†
vivado の Block Diagram に axi_gpio を置き、ダブルクリック。 GPIO Width に適当な値を入れる。 (ここでは 5 本の入出力ピンを確保した)
GPIO ポートの上で右クリック後 [Make External] する。
追加された GPIO_1 ポートをクリックして、プロパティエディタで適当な名前に変更。
Diagram の右クリックから [Run Connection Automation] すると、
Processor System Reset および AXI Interconnect が追加され、接続が完了する。
design_1_wrapper の編集†
design_1 は Dialog から生成されるのだけれど、 design_1_wrapper は手動で変更しなければならないらしい?
design_1 からの DATAPINS_tri_? を正しくトライステート動作するよう外部へ繋ぐ。
LANG:verilog module design_1_wrapper( ... //user define io, DATAPINS, ... ); inout [4:0] DATAPINS; .... // user wire define code wire [4:0] DATAPINS_i, DATAPINS_o, DATAPINS_t; ... //user logic example /* IOBUF DATAPINS_0_iobuf (.I(DATAPINS_o[0]), .IO(DATAPINS[0]), .O(DATAPINS_i[0]), .T(DATAPINS_t[0])); IOBUF DATAPINS_1_iobuf (.I(DATAPINS_o[1]), .IO(DATAPINS[1]), .O(DATAPINS_i[1]), .T(DATAPINS_t[1])); IOBUF DATAPINS_2_iobuf (.I(DATAPINS_o[2]), .IO(DATAPINS[2]), .O(DATAPINS_i[2]), .T(DATAPINS_t[2])); IOBUF DATAPINS_3_iobuf (.I(DATAPINS_o[3]), .IO(DATAPINS[3]), .O(DATAPINS_i[3]), .T(DATAPINS_t[3])); IOBUF DATAPINS_4_iobuf (.I(DATAPINS_o[4]), .IO(DATAPINS[4]), .O(DATAPINS_i[4]), .T(DATAPINS_t[4])); */ assign DATAPINS_i = DATAPINS; assign DATAPINS = DATAPINS_t ? 5'bzzzzz : DATAPINS_o; design_1 design_1_i( ... .DATAPINS_tri_i(DATAPINS_i), .DATAPINS_tri_o(DATAPINS_o), .DATAPINS_tri_t(DATAPINS_t), ... );
ピン配置の指定†
xdc ファイルの中で PACKAGE_PIN を指定する。
LANG:xdc set_property PACKAGE_PIN A20 [get_ports {DATAPINS[4]}] ;# IO_B35_LN[2] set_property IOSTANDARD LVCMOS33 [get_ports {DATAPINS[4]}] ;# IO_B35_LN[2]
xdc ファイル内のコードに続くコメントは、# の前に ; を付けておかないと コンパイラのバグでエラーになるらしい
vivado 上でビルド†
vivado 上で Generate Block Design 後、Generate Bitstream する。
あれ?
[Synth 8-439] module 'design_1_processing_system7_0_0' not found ["(snip).srcs/sources_1/bd/design_1/synth/design_1.v":239]
のエラーが出た。
デザインからの出力をいったんクリアする†
[Design Sources]-[design_1_wrapper]-[design_1_i] の上で右クリックから、 [Reset Output Products] してからビルドしたところうまくいった。
FPGA をプログラムする†
(project).runs\impl_1\design_1_wrapper.bin を zynq の ~/lcd-control/design_1_wrapper.bin にコピーして、プログラマブルロジック書き換え用スクリプト で FPGA をプログラムする。
LANG:console $ cd ~/lcd-control $ sudo fpga-program design_1_wrapper.bin
axi_gpio ドライバを使う†
Device Tree の更新†
上のようにして追加した axi_gpio は petalinux が自動で作る system.dtb に現れるんだろうか?
試してみたいところだが、system.dtb のみをビルドする方法が分からない
普通にやるなら hdf を petalinux へ持って行って、 もう一度 petalinux-configure してから petalinux-build だけど、 これをやると小一時間かかるのでいくらなんでも・・・
そこで、
- https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841846/AXI+GPIO
- https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf#page=20
を見ながら手動で設定してしまう。
Device Tree書き換え用スクリプト dto を使う。
LANG:console $ cat << EOT > gpio1.dtso > // Device Tree File for gpio1 > /dts-v1/; > /plugin/; > / { > fragment@0 { > target-path = "/amba"; > __overlay__ { > #address-cells = <1>; > #size-cells = <1>; > axi_gpio_0: axi_gpio@41200000 { > #gpio-cells = <3>; > clock-names = "s_axi_aclk"; > clocks = <&clkc 15>; > compatible = "xlnx,axi-gpio-2.0", "xlnx,xps-gpio-1.00.a"; > gpio-controller ; > reg = <0x41200000 0x10000>; > xlnx,all-inputs = <0x0>; > xlnx,all-inputs-2 = <0x0>; > xlnx,all-outputs = <0x0>; > xlnx,all-outputs-2 = <0x0>; > xlnx,dout-default = <0x00000000>; > xlnx,dout-default-2 = <0x00000000>; > xlnx,gpio-width = <0x5>; > xlnx,gpio2-width = <0x0>; > xlnx,interrupt-present = <0x0>; > xlnx,is-dual = <0x0>; > xlnx,tri-default = <0xFFFFFFFF>; > xlnx,tri-default-2 = <0xFFFFFFFF>; > }; > }; > }; > }; > EOT $ sudo dto gpio1 gpio1.dtso <stdout>: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property <stdout>: Warning (clocks_property): Property 'clocks', cell 1 is not a phandle reference in /fragment@0/__overlay__/axi_gpio@41200000 <stdout>: Warning (clocks_property): Could not get phandle node for /fragment@0/__overlay__/axi_gpio@41200000:clocks(cell 1) $ ls /proc/device-tree/amba/axi_gpio@41200000/ '#gpio-cells' gpio-controller xlnx,all-inputs-2 xlnx,dout-default-2 xlnx,is-dual clock-names name xlnx,all-outputs xlnx,gpio-width xlnx,tri-default clocks reg xlnx,all-outputs-2 xlnx,gpio2-width xlnx,tri-default-2 compatible xlnx,all-inputs xlnx,dout-default xlnx,interrupt-present $ ls /dev/gpiochip1 /dev/gpiochip1
これでデバイスが追加された。
単純な入出力をやってみる†
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841846/AXI+GPIO に使い方がある。
どうやらとても簡単。
/sys/class/gpio/gpio*/value への読み書きがそのままピンへの読み書きになる模様。
LANG:console $ # 再起動後 $ ls -F /sys/class/gpio/ export gpiochip906@ unexport $ sudo fpga-program design_1_wrapper.bin <stdout>: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property $ sudo dto gpio1 gpio1.dtso <stdout>: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property <stdout>: Warning (clocks_property): Property 'clocks', cell 1 is not a phandle reference in /fragment@0/__overlay__/axi_gpio@41200000 <stdout>: Warning (clocks_property): Could not get phandle node for /fragment@0/__overlay__/axi_gpio@41200000:clocks(cell 1) $ ls -F /sys/class/gpio/ export gpiochip901@ gpiochip906@ unexport
上記で /dev/gpiochip1 が追加されたのと同じ手順で /sys/class/gpio/gpiochip901 が追加されたことが分かる。
もう1つが gpiochip906 となっているのは、901-905 の 5 つの番号が axi_gpio に指定した5本の IO ピンに相当するため、もう1つの gpiochip は 906- の番号が割り振られているようだ。
これらのピンにアクセスするには、対応する番号を /sys/class/gpio/export に書き込むと現れる /sys/class/gpio/gpio901/value を使えば良い。
LANG:console $ echo 901 | sudo tee /sys/class/gpio/export 901 $ ls -F /sys/class/gpio/ export gpio901@ gpiochip901@ gpiochip906@ unexport $ # gpio901 というのが増えた $ for n in 902 903 904 905; do > echo $n | sudo tee /sys/class/gpio/export > done 902 903 904 905 $ ls -F /sys/class/gpio/ # 5 つの io ピンに対応する gpio901-905 が登録された export gpio902@ gpio904@ gpiochip901@ unexport gpio901@ gpio903@ gpio905@ gpiochip906@ $ ls -F /sys/class/gpio/gpio901/ active_low device@ direction edge power/ subsystem@ uevent value $ cat /sys/class/gpio/gpio901/direction # 現在は入力に設定されている in $ cat /sys/class/gpio/gpio901/value # sudo は必要なく、誰でも読める 1 $ echo out | sudo tee /sys/class/gpio/gpio901/direction # 出力に変更 out $ cat /sys/class/gpio/gpio901/value 0 $ echo 1 | sudo tee /sys/class/gpio/gpio901/value # 書き込む 1 $ cat /sys/class/gpio/gpio901/value 1
うん、どうやらうまく行ってそう。
出力に切り替えた瞬間から指定の値を出すにはどうするのか?†
出力に切り替える前に出力値を変更しようとするとエラーになってしまう。
LANG:console $ echo 1 | sudo tee /sys/class/gpio/gpio905/value tee: /sys/class/gpio/gpio905/value: Operation not permitted
切り替えた瞬間から 0 あるいは 1 のうち好ましい方を出すには・・・
https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf
IP のマニュアルを見ても、そういう動作はできなさそうだ。
どうしても始めから決まった値を出したければ、 Default Tri State Value の対応するビットを 0 にした上で、 Default Output Value の対応するビットを設定しておくしかなさそう。
デバイスツリーへの登録前から GPIO 本数が既知なのはなぜなのか†
上の様子をよく見ると、今回追加した axi_gpio をデバイスツリーに登録する前から、 デフォルトの汎用 gpio が /sys/class/gpio/gpiochip906 として登録されていた。
これは、今回追加した axi_gpio が入出力線を 5 本持っているためで、 901-905 を欠番として空けておいてくれたのだと思う。
つまり、今回追加した axi_gpio をデバイスツリーに登録する前から、 Linux kernel は未登録の gpio が 5 本あることを知っていたことになる。
これってどういう理屈なんだろう?
割り込みの使い方†
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841846/AXI+GPIO
の方法を使うには、カーネルの設定にて
CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_GPIO_POLLED=y
が必要であると書かれていたが、petalinux をデフォルトで使っている限り
LANG:console $ grep CONFIG_KEYBOARD_GPIO kernel-source/.config CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_GPIO_POLLED=y
となっているため問題ないみたい。
今ちょっと試せないのだけれど、かなり便利そうだ。
generic-uio を使ってアクセスする†
上記の axi_gpio ドライバではピンごとにアクセスすることになるため、 複数ピンをまとめて読み書きする用途には向かない。
そういう用途には generic-uio を使って自分でレジスタを叩くのがいいような?
以下、https://qiita.com/take-iwiw/items/da91ce4dc2a8a8df3c0a などを参考に:
vivado のブロックデザインから AXI GPIO IP をダブルクリックして、 Documentation をクリックすると、IP の Help を参照できる。
を見ると、割り込みを使わない限りは
- 0x0000 : GPIO_DATA
- 0x0004 : GPIO_TRI (1 のビットが入力になる)
- 0x0008 : GPIO2_DATA (dual channnel の時のみ有効)
- 0x000C : GPIO2_TRI (dual channnel の時のみ有効)
なので、今の場合は 0x0000 と 0x0004 とにアクセスできればそれでいい。
LANG:console $ cat << EOT > gpio1_generic-uio.dtso // Device Tree File for gpio1 /dts-v1/; /plugin/; / { fragment@0 { target-path = "/amba"; __overlay__ { #address-cells = <1>; #size-cells = <1>; uio@41200000 { compatible = "generic-uio"; reg = <0x41200000 0x1000>; }; }; }; }; EOT $ sudo dto gpio1 gpio1_generic-uio.dtso <stdout>: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property $ ls /proc/device-tree/amba/uio@41200000/ compatible name reg $ ls /sys/class/uio/uio0 dev device event maps name power subsystem uevent version $ find /sys/class/uio/uio0/maps/map0 -type f -print -exec cat {} \; /sys/class/uio/uio0/maps/map0/offset 0x0 /sys/class/uio/uio0/maps/map0/size 0x00001000 /sys/class/uio/uio0/maps/map0/name /amba/gpio1@41200000 /sys/class/uio/uio0/maps/map0/addr 0x41200000
汎用制御プログラム uio†
そこで uio という汎用コマンドを作った。
uio : https://github.com/osamutake/zynq-utils/tree/master/src/uio
使い方:
LANG:console $ gcc uio_cmd.c uio.c -o uio $ ./uio USAGE: uio uio_num byte_offset [value] $ sudo ./uio 0 0 # /dev/uio0 のオフセット 0 を読んで返す 0x0000001f $ sudo ./uio 0 0x0004 # /dev/uio0 のオフセット 4 を読んで返す 0x0000001f $ sudo ./uio 0 3 # /dev/uio0 のオフセット 3 を読んで返す?! byte_offset should be an integer multiple of four. $ sudo ./uio 0 4 0x000f # /dev/uio0 のオフセット 4 に 0x000f を書き込む $ sudo ./uio 0 0 # /dev/uio0 のオフセット 0 を読んで返す 0x0000000f $ sudo ./uio 0 0 0x0010 # /dev/uio0 のオフセット 0 に 0x0010 を書き込む $ sudo ./uio 0 0 # /dev/uio0 のオフセット 0 を読んで返す 0x0000001f $ # オフセット 0 の下位 4 ビットは書き込み不可なので変更されなかった
こちらもちゃんと動いてるみたい。
あ、マニュアルには出力に割り当てられたピンのデータビットを読むとゼロが返ると書かれていたけれど、 実際には直前に出力した値が返っているようで、その点は注意が必要だ。
/dev/mem を使ってアクセスする†
という方法もあるらしいのだけれど、 uio の方が分かりやすいと思うのであまり踏み込まないことにする。
LANG:console $ dtc -O dts /boot/devicetree.dtb | less ... memory { device_type = "memory"; reg = <0x0 0x40000000>; }; ... $ grep od ~/.bashrc alias od='od -tx1z -Ax' $ od /proc/device-tree/memory/reg 000000 00 00 00 00 40 00 00 00 >....@...< 000008
えーと、これだと 0x00000000 - 0x40000000 しかアクセスできないのか。
0x41200000 あたりにアクセスするにはどうするのがいいのだろう???
作業履歴へ戻る → 電気回路/zynq