zynq/Petalinux2018.3でaxi_gpio のバックアップ差分(No.8)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[電気回路/zynq]]

* 概要 [#jf91e804]

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

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

#contents

* ロジックの作成 [#r4ed570e]

** ブロックダイアグラムに axi_gpio IP を追加 [#cf1a998e]

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

&ref(電気回路/zynq/Petalinux2018.3でキャラクタLCD制御(ST7032i)/axi_gpio-width.png,,33%);

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

&ref(電気回路/zynq/Petalinux2018.3でキャラクタLCD制御(ST7032i)/axi_gpio-make_external.png,,50%);

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

&ref(電気回路/zynq/Petalinux2018.3でキャラクタLCD制御(ST7032i)/axi_gpio-port-rename.png,,50%);

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

&ref(電気回路/zynq/Petalinux2018.3でキャラクタLCD制御(ST7032i)/axi_gpio-connection.png,,33%);

Processor System Reset および AXI Interconnect が追加され、接続が完了する。


** design_1_wrapper の編集 [#td6a4ab7]

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),
        ...
 );

** ピン配置の指定 [#ie814751]

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]

- コメントは # の前に ; を付けておかないとエラーになるらしい

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

** vivado 上でビルド [#u89f9a56]

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]

のエラーが出た。

*** デザインからの出力をいったんクリアする [#a4721930]

[Design Sources]-[design_1_wrapper]-[design_1_i] の上で右クリックから、
[Reset Output Products] してからビルドしたところうまくいった。

&ref(電気回路/zynq/I2CでLCD(NHD-C0216CIZ)を制御する/design-reset-output.png,,50%);
** FPGA をプログラムする [#i092c854]

(project).runs\impl_1\design_1_wrapper.bin を zynq の ~/lcd-control/design_1_wrapper.bin 
にコピーして、[[プログラマブルロジック書き換え用スクリプト>電気回路/zynq/Petalinux2018.3でPLとDevice Treeを動的に変更する#gb07ac62]] で FPGA をプログラムする。

 LANG:console
 $ cd ~/lcd-control
 $ sudo fpga-load design_1_wrapper.bin

* axi_gpio ドライバを使う [#l724193d]

** Device Tree の更新 [#tab2c783]

上のようにして追加した 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>電気回路/zynq/Petalinux2018.3でPLとDevice Treeを動的に変更する#b87fd39f]] を使う。

 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

これでデバイスが追加された。

** 単純な入出力をやってみる [#oe8a42b7]

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 
 $ 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 が追加されたのと同じ手順で gpiochip901 が追加されたことが分かる。

もう1つが gpiochip906 となっているのは、901-906 の 5 つの番号が axi_gpio に指定した5本の IO ピンに相当するためである。

これらのピンにアクセスするには、対応する番号を /sys/class/gpio/export に書き込めばいい。

 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

うん、どうやらうまく行ってそう。
*** 出力に切り替えた瞬間から指定の値を出すにはどうするのか? [#c2595104]

 LANG:console
 $ echo out | sudo tee /sys/class/gpio/gpio905/direction
出力に切り替える前に出力値を変更しようとするとエラーになってしまう。

する前に出力値を変更しようとするとエラーになってしまう。

 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 本数が既知なのはなぜなのか [#fbd66a0a]

上の様子をよく見ると、今回追加した axi_gpio をデバイスツリーに登録する前から、
デフォルトの汎用 gpio が /sys/class/gpio/gpiochip906 として登録されていた。

これは、今回追加した axi_gpio が入出力線を 5 本持っているためで、
901-905 を欠番として空けておいてくれたのだと思う。

つまり、今回追加した axi_gpio をデバイスツリーに登録する前から、
Linux kernel は未登録の gpio が 5 本あることを知っていたことになる。

これってどういう理屈なんだろう?

** 割り込みの使い方 [#e2d15282]

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 を使ってアクセスする [#p4eccd43]

上記の axi_gpio ドライバではピンごとにアクセスすることになるため、
複数ピンをまとめて読み書きする用途には向かない。

そういう用途には generic-uio を使って自分でレジスタを叩くのがいいような?

以下、https://qiita.com/take-iwiw/items/da91ce4dc2a8a8df3c0a などを参考に:

vivado のブロックデザインから AXI GPIO IP をダブルクリックして、
Documentation をクリックすると、IP の Help を参照できる。

&ref(axi-gpio_help.png,,50%);

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 0x0008>;
        };
      };
    };
  };
  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
  0x00000008
  /sys/class/uio/uio0/maps/map0/name
  /amba/gpio1@41200000
  /sys/class/uio/uio0/maps/map0/addr
  0x41200000

** 汎用制御プログラム uio [#ma01034d]

そこで uio という汎用コマンドを作った。

#gist(osamutake/c0ca68ddd369710e1e46ce9ce82a5988);

使い方:

 LANG:console
 $ gcc uio.c -o uio
 $ 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 に 0x000f を書き込む
 $ sudo ./uio 0 0        # /dev/uio0 のオフセット 0 を読んで返す
  0x0000001f
 $ 

こちらもちゃんと動いてるみたい。

あ、マニュアルには出力に割り当てられたピンのデータビットを読むとゼロが返ると書かれていたけれど、
実際には直前に出力した値が返っているようで、その点は注意が必要だ。
* /dev/mem を使ってアクセスする [#cf767cc4]

 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 あたりにアクセスするにはどうするのがいいのだろう???

* コメント・質問 [#ved01a20]

#article_kcaptcha


Counter: 8837 (from 2010/06/03), today: 1, yesterday: 0