zynq/Petalinux2018.3でaxi_gpio

(24d) 更新


電気回路/zynq

概要

汎用 GPIO モジュールである axi_gpio を使い、Zynq の CPU から FPGA の入出力ピンを制御したい。

いわゆるLチカに相当するやつだ。

ここではキャラクタ LCD のリセットピンを制御したかった。

ロジックの作成

ブロックダイアグラムに axi_gpio IP を追加

vivado の Block Diagram に axi_gpio を置き、ダブルクリック。 GPIO Width に適当な値を入れる。 (ここでは 5 本の入出力ピンを確保した)

axi_gpio-width.png

GPIO ポートの上で右クリック後 [Make External] する。

axi_gpio-make_external.png

追加された GPIO_1 ポートをクリックして、プロパティエディタで適当な名前に変更。

axi_gpio-port-rename.png

Diagram の右クリックから [Run Connection Automation] すると、

axi_gpio-connection.png

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 ファイル内のコードに続くコメントは、# の前に ; を付けておかないと コンパイラのバグでエラーになるらしい

https://japan.xilinx.com/support/answers/51613.html

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] してからビルドしたところうまくいった。

design-reset-output.png

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 だけど、 これをやると小一時間かかるのでいくらなんでも・・・

そこで、

を見ながら手動で設定してしまう。

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 を参照できる。

axi-gpio_help.png

https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/v2_0/pg144-axi-gpio.pdf#page=10

を見ると、割り込みを使わない限りは

  • 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

コメント・質問




署名忘れました

杉島逑昭? ()

昨日メールさせていただいた杉島です。
署名を忘れました。失礼いたしました。

会社名 株式会社シニックス
代表者名 杉島逑昭(Sugishima Nobuaki)
所在地 東京都豊島区長崎1-25-22 〒171-0051
電話 03-3957-0030
メール sugishima@senixtec.com

gpio割り込みの元

杉島逑昭? ()

先生の記事はいつも参考にさせていただいています。
多忙な教育、研究生活、とてもサポートめいたレターに返信していただけるお時間はないと思いつつ、あえて簡単な質問をさせていただきます。
Microzed、vivado2019.1, petalinux2016.2です。
第1の疑問
割り込みは一般にinputに設定したgpioの割り込み端子を使うらしいです。これには外部からの入力端子がありません。であるためaxi_periph経由で”割り込みの元”を受信することになると思います。ここで遅れが発生します。なにか誤解?
次の疑問は割り込みを受けたlinux側の処置に関するものです。
http://myokota.hatenablog.jp/entry/2015/08/12/233020
という参考例がありました。

       sw.fd = open("/sys/class/gpio/gpio902/value", O_RDONLY);
       sw.events = POLLPRI;
       while (1) {
               poll(&sw, 1, -1);
               lseek(sw.fd, 0, SEEK_SET);
               read(sw.fd, buf, 1);
               printf("pushed!\n");
       }

これはPollingであって割り込みではありません。
即応性のある割り込み動作は期待できません。
ARMの割り込みがこのようなものであるとは思えません。
gpio方式に至る前はmy_ipから1bitの割り込み要求端子を出し、これをZynqの
IRQ_F2P[15:0]に接続していました。vivado最上位にこれを上げてありますが、intrという信号をそれより上位が割り込みとして認識していないのかもしれません。動作はNGです。

以前Suzaku @AtmarkTechoを使用していたときは割り込みこれほど手こずることはありませんでした。

以上厚顔を顧みずメールしています。
Linux側Sourceをお送りすることも可能です。

  • 私自身、ZYNQ + Linux での割り込み処理にまだ手を出していないため直接のお答えができずすみません。自分で調べるとしたらキーワードは "zynq request_irq" あたりかな、と予想します。良い情報に巡り会えると良いのですが・・・(質問の文章からすると、割り込み処理の仕組み自体についてもう少し調査されることも必要かもしれないと感じました) -- 武内(管理人)?

添付ファイル: fileaxi-gpio_help.png 138件 [詳細]

Counter: 888 (from 2010/06/03), today: 9, yesterday: 5