Petalinux2018.3でキャラクタLCD制御(ST7032i) のバックアップの現在との差分(No.3)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[電気回路/zynq/Petalinux2018.3環境を整える]]
[[電気回路/zynq]]

* I2C 経由で繋いだキャラクタ型の液晶ディスプレイ(LCD)を制御する [#b227347f]

繋いだのはこれ [[digikey:NHD-C0216CIZ-FSW-FBW-3V3-ND]]

16 文字 x 2 行 で白色バックライト付きの LCD

付属の PDF だけだといろいろ情報が足りなくて困るのだけれど、
どうやらこの LCD には ST7032i という有名なコントローラが載っているらしいので、
このキーワードで検索すればいろいろと情報が得られる。
どうやらこの LCD には [[Strawberry Linux の ST7032i>https://strawberry-linux.com/pub/ST7032i.pdf]] 
という有名なコントローラが載っているらしいので、このキーワードで検索すればいろいろと情報が得られる。

ST7032i のマニュアル:~
https://strawberry-linux.com/pub/ST7032i.pdf

** 目次 [#v08453ec]

#contents

* 環境 [#h9cf1202]

[[作業履歴>電気回路/zynq/Petalinux2018.3環境を整える#wa39ab55]] のように
Petalinux 2018.3 を基本として作業しています。
[[作業履歴>電気回路/zynq#kc37dfd8]] のように
Petalinux 2018.3 を基本として作業してます。

LCD は i2c-1 に繋がっているはず。
LCD は i2c-1 に繋いだ。Petalinux は i2c-1 を認識して dtb に反映してくれていた。

Petalinux は i2c-1 を認識して dtb に反映してくれています。

Zynq 機器で動作する Ubuntu 18.04LTC のコンソールで、

 LANG:console
 $ less /boot/system.dts
  ...
 
  i2c1: i2c@e0005000 {
   compatible = "cdns,i2c-r1p10";
   status = "disabled";
   clocks = <&clkc 39>;
   interrupt-parent = <&intc>;
   interrupts = <0 48 4>;
   reg = <0xe0005000 0x1000>;
   #address-cells = <1>;
   #size-cells = <0>;
  };
  ...
 
 $ sudo grep i2c /var/log/syslog
  Jan 28 15:58:20 zturn2018_3 kernel: i2c /dev entries driver
  Jan 28 15:58:20 zturn2018_3 kernel: cdns-i2c e0004000.i2c: 400 kHz mmio e0004000 irq 24
  Jan 28 15:58:20 zturn2018_3 kernel: cdns-i2c e0005000.i2c: 400 kHz mmio e0005000 irq 25
 $ ls -l /dev | grep i2c
  crw------- 1 root     root     89,   0  1月 28 15:58 i2c-0
  crw------- 1 root     root     89,   1  1月 28 15:58 i2c-1
 $ cat /proc/device-tree/amba/i2c@e0005000/status
  okay

ちゃんと 400kHz クロックになっている。
ちゃんと 400kHz クロックになっており、status = "okay" なのでそのまま使える。

* まずはリセットピンを制御可能にする [#e9ee91d6]
* リセットピンを制御可能にする [#e9ee91d6]

** axi_gpio の追加 [#cf1a998e]
[[電気回路/zynq/Petalinux2018.3でaxi_gpio]] により、
/sys/class/gpio/gpio905/value 経由でリセットピンの上げ下げができるようになった。

CPU から axi_gpio 経由で制御する。
リセットは Active Low なので、使用時には上げておく必要がある。

vivado の Block Diagram に axi_gpio を置き、ダブルクリック。
GPIO Width に適当な値を入れる。
(ここでは LCD_RST ピン以外も制御したかったため5を入れた)
 LANG:console
 $ echo 905 > /sys/class/gpio/export
 $ echo out > /sys/class/gpio/gpio905/direction
 $ echo 0 > /sys/class/gpio/gpio905/value # reset
 $ echo 1 > /sys/class/gpio/gpio905/value # release reset

&ref(axi_gpio-width.png,,33%);
* LCD との通信 [#cdba4dc7]

GPIO ポートの上で右クリック後 [Make External] する。
LCD のアドレスは 0x7c だそうだ(後で間違いであったことが発覚)。

&ref(axi_gpio-make_external.png,,50%);
- int i2c = open("/dev/i2c-1", O_RDWR);
- ioctl(i2c, I2C_SLAVE, 0x7c);
- write(i2c, &bytes, length_of_bytes);
- read(i2c, &buffer, length_to_receive);

追加された GPIO_1 ポートをクリックして、プロパティエディタで適当な名前に変更。
の組み合わせでやりとりすればいい・・・のかと思ったのだけれど、

&ref(axi_gpio-port-rename.png,,50%);
https://www.kernel.org/doc/Documentation/i2c/dev-interface

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

&ref(axi_gpio-connection.png,,33%);
> Note that only a subset of the I2C and SMBus protocols can be achieved by
the means of read() and write() calls. In particular, so-called combined
transactions (mixing read and write messages in the same transaction)
aren't supported. For this reason, this interface is almost never used by
user-space programs.

Processor System Reset および AXI Interconnect が追加され、接続が完了する。
などと書かれてた orz

** design_1_wrapper の編集 [#td6a4ab7]
LCD の制御だけが目的の場合にはデータの読み出しを必要としないので、
まあそこそこ行ける・・・かな?

design_1 は Dialog から生成されるのだけれど、
design_1_wrapper は手動で変更しなければならないらしい?
試してみる。

 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]));
 
 design_1 design_1_i(
        ...
        .DATAPINS_tri_i(DATAPINS_i),
        .DATAPINS_tri_o(DATAPINS_o),
        .DATAPINS_tri_t(DATAPINS_i),
        ...
 );
* ちょっとうまく行かないので調べ中 [#f00a2f54]

** ピン配置の指定 [#ie814751]
まずはこれだけ。

xdc ファイルの中で PACKAGE_PIN を指定する。
 LANG:c
  int i2c = open("/dev/i2c-1",O_RDWR);
  ioctl(i2c, I2C_SLAVE, 0x7c);
  write(i2c, "\x00\x38", 2);
  close(i2c);

 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]
オシロスコープで見た信号は、

- コメントは # の前に ; を付けておかないとエラーになるらしい
&tchart(
@w_hold 5
@w_transient 1
SCL ~~~___|~~__|~~__|~~__|~~__|~~__|~~__|~~__|~~__|~~__|~~~~~
SDA ~~|___~~~~~~~~~~~~~~~~~~~~____________~~~~____|~~
DATA -=S=--=1=--=1=--=1=--=1=--=1=--=0=--=0=--=0=--=1=--=0=-=P=-
HEX  -----==========7=X==============C=XW===XNA===-------
);

https://japan.xilinx.com/support/answers/51613.html
のようになっていた。

** vivado 上でビルド [#u89f9a56]
例えば http://www.picfun.com/c15.html を参考に波形を読み解くと、

vivado 上で Generate Block Design 後、Generate Bitstream する。
- SCL = 1 における SDA negedge がスタートコンディション (S)
- 以降、SCL = 0 でのみ SDA の遷移が許される
- SCL posedge にてデータの読み取り
- 1バイト目は 7bit のスレーブアドレスに R/W フラグを付けたもの
- 今の場合は (0x7C << 1) + (write ? 0 : 1) の write モードになる
- スレーブが自分のアドレスを受け取ると、上のチャートで NA となっているところで SDA を 0 にする(Acknowledge)のだが、実測ではここが1になっているのでスレーブが反応していない状況だ
- SCL = 1 における SDA posedge がストップコンディション (P)
- ストップを送るためには SDA を一旦 0 にしなければならないので、直前にマスターが 0 を出している

あれ?
思った通りの信号が出ているにもかかわらずスレーブが応答しない理由は・・・まさかアドレスが間違ってる?!

 [Synth 8-439] module 'design_1_processing_system7_0_0' not found ["(snip).srcs/sources_1/bd/design_1/synth/design_1.v":239]
もう一度 LCD のマニュアルを見直すと、

のエラーが出た。
#ref(lcd-address.png,,50%,around);

[Design Sources]-[design_1_wrapper]-[design_1_i] の上で右クリックから、
[Reset Output Products] してからビルドしたところうまくいった。
でかでかと Slave Address = 0x7C と書いておきながら、
サンプルコード等で実際に送っているアドレスは 0b0111110 = 0x3e だった orz

&ref(design-reset-output.png,,50%);
** PL をプログラムする [#i092c854]
すっかり騙されたよ。

(project).runs\impl_1\design_1_wrapper.bin を ~/lcd-control/design_1_wrapper.bin にコピー
ioctl(i2c, I2C_SLAVE, 0x3e); にしたところ、うまく行き始めた。

* 操作プログラム [#k04ab626]

こういうプログラムを作った。

lcd-control : https://github.com/osamutake/zynq-utils/tree/master/src/lcd-control

マニュアルによれば、これを使って、

 LANG:console
 $ cd ~/lcd-control
 $ sudo fpga-load design_1_wrapper.bin
 $ sudo lcd-control -c 0x38
 $ sudo lcd-control -c 0x39
 $ sudo lcd-control -c 0x14 0x78 0x5e 0x6d 0x0c 0x06 0x01
 $ sudo lcd-control "Hello, world!"

** Device Tree の更新 [#tab2c783]
で良いはずなのだけれど、実際には

上のようにして追加した axi_gpio は petalinux の作る system.dtb に現れるんだろうか?
- コントラスト、というか、黒が強すぎる
- 1行目の上半分が表示されない

試してみたいところだが、system.dtb のみをビルドする方法が分からない
の不具合があった。

普通にやるなら hdf を petalinux へ持って行って、
もう一度 petalinux-configure してから petalinux-build だけど、
これをやると小一時間かかるのでいくらなんでも・・・
コントラストについては、0x5e となっていたところを 0x5d にすれば良くなった。

そこで、
https://okchan08.hateblo.jp/entry/2019/02/01/190000
を見ながら手動で設定してしまう。
1行目の上半分が表示されないのは、たまたま当たってしまった LCD の初期不良な可能性が高い。~
→ LCD を指で押すと表示されない領域が増えたり減ったりする orz

ということで、以下のようにして使えるようになった。

 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
 $ ls /sys/kernel/config/device-tree/overlays/
 $ sudo mkdir -p /sys/kernel/config/device-tree/overlays/gpio1
 $ ls /sys/kernel/config/device-tree/overlays/gpio1
  dtbo  path  status
 $ sudo dtc -o dtb gpio1.dtso -o /sys/kernel/config/device-tree/overlays/gpio1/dtbo
 $ 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
 $ # 初期化
 $ sudo lcd-control -c 0x38
 $ sudo lcd-control -c 0x39
 $ sudo lcd-control -c 0x14 0x78 0x5d 0x6d 0x0c 0x06
 
 $ # 通常の使い方
 $ sudo lcd-control -c 0x01 # 画面消去&ホームポジション
 $ sudo lcd-control -c 0xc0 # 2行目先頭へ移動
 $ sudo lcd-control "Hello, world!"
 
 $ # IP アドレスを表示
 $ sudo lcd-control -c 0x01 # 画面消去&ホームポジション
 $ sudo lcd-control -c 0xc0 # 2行目先頭へ移動
 $ sudo lcd-control `ip a s eth0 | grep "inet " | sed -e "s/.*inet //" | sed -e "s/\/.*//"`

/dev/gpiochip1 というデバイスができた。
元々この LCD は Ethernet 経由でデバイスを制御する目的で
IP アドレスを表示するためのものだったので、さしあたりは
1行しか表示できなくてもまあなんとかなりそう。

https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841846/AXI+GPIO
* LAN ケーブルの抜き差しに応じて表示を変更する [#x7edb681]

に使い方がある。
eth0 が有効・無効になった直後に LCD 表示を更新する。

* LCD との通信 [#cdba4dc7]
/usr/local/bin/lcd-show-ip
 LANG:sh
 #!/bin/sh
 
 test -e /sys/class/gpio/gpio905 || exit 1
 
 # Deassert active-low reset
 echo out > /sys/class/gpio/gpio905/direction
 echo 0 > /sys/class/gpio/gpio905/value # reset
 echo 1 > /sys/class/gpio/gpio905/value # release reset
 
 
 lcd-control -c 0x38
 lcd-control -c 0x39
 lcd-control -c 0x14 0x7c 0x5d 0x6d 0x0c 0x06 0x01
 
 lcd-control -c 0x01 # clear screen
 lcd-control -c 0xc0 # move to second line
 
 # show ip address
 ADDR=`ip a s eth0 | grep "inet " | sed -e "s/.*inet //" | sed -e "s/\/.*//"`
 if [ -z $ADDR ]; then
    lcd-control "NO ADDRESS"
 else
    lcd-control $ADDR
 fi

LCD のアドレスは 0x7c だそうで、
を用意して、

 $ mkdir lcd-control
 $ cd lcd-control
 $ git init
 $ jed lcd-control.c
 LANG:console
 $ sudo chown root:root /usr/local/bin/lcd-show-ip
 $ sudo ln -s /usr/local/bin/lcd-show-ip /usr/lib/networkd-dispatcher/routable.d/lcd-show-ip
 $ sudo ln -s /usr/local/bin/lcd-show-ip /usr/lib/networkd-dispatcher/no-carrier.d/lcd-show-ip

としたところ、
- LAN ケーブルを抜くと NO ADDRESS を表示
- LAN ケーブルを挿すと dhcp で自動取得した IP アドレスを表示

を実現できた。

作業履歴へ戻る → [[電気回路/zynq]]

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

#article_kcaptcha


Counter: 3311 (from 2010/06/03), today: 3, yesterday: 3