1.記事一覧
記事は複数回に分けて投稿します。XilinxのARM SoC (Zynq7020SoC)で動作する、YoctoベースのPetaLinuxを使わない素のLinuxをソースコードから構築する記事の続編です。
エッジArm Linux構築編(Armv7)
- 【Zynq7】Yoctoを使わずに素のLinuxをソースから構築する
- 【Zynq7】1.FSBL(1stStageBootLoader)の構築
- 【Zynq7】2.U-bootの構築
- 【Zynq7】3.Linuxカーネルの構築
- 【Zynq7】4.Zyboz7ボードのデバイスツリー構築
- 【Zynq7】5.RootFSのマウント(Ubuntu18.04)
- 【Zynq7】6.カーネルモジュールの配置
- 【Zynq7】番外編1.u-bootが途中で止まる問題
- 【Zynq7】番外編2.MACアドレスをQSPIFlashから読み出して設定する
エッジArm Linux実践編(Armv7)
- 【Zynq7】実践1. LinuxにRTCを認識させる【I2Cデバイス編】
- 【Zynq7】実践2. 温度センサをPython+Dockerで使う【I2Cデバイス】
- 【Zynq7】実践3. 組込みLinuxでコンテナ導入に苦労した話【Docker Engine編】
- 【Zynq7】実践4. LinuxでSPIを使う【液晶編1】
- 【Zynq7】実践5. ZynqPLのGPIOをLinuxで使う【液晶編2】 ←本ページ
- 【Zynq7】実践6. PythonでSPI液晶を使う【液晶編3】
- 【Zynq7】閑話. SPI バス周波数が上がらない問題
2.【概要】PythonでGPIOを使いたい
240×320ピクセルのSPI液晶(ILI9328)を使う編の第2回になります。前回はこちら。この液晶はSPIインターフェースの他にGPIOの制御が必要なので、今回はZynq7000のPL(=FPGA)上にAXI-GPIOのIPを構築し、それをLinux上から使えるようにしてPythonで使えるようにします。
手順は以下のようになります。
- AXI-GPIOのIPを構築し、配線情報を追加したBitstreamを生成する
- Linuxデバイスツリーを更新し、GPIOをシステムから使えるようにする
前回のSPIを使う記事とほぼ同じ手順なので、前回まとめてやったほうが手間がかからないです。
液晶制御に必要なGPIOはRS,Resetの2線になります。
【追記】RS信号は不要でした。SPIのスタートバイトにRSのビットフィールドがあります。ただし、次回の液晶編でSPIのCS信号がうまく使えないことが判明したので、GPIOでCS信号をソフト制御します。よって2線必要です。
今回使用する環境です。
開発PC | Ubuntu20.04 |
開発ツール | Xilinx社 Vivado・Vitis v2022.2 |
ターゲットボード | Digilent社製 Zybo z7-20 SoC:Xilinx社 Zynq7020 armv7hf (Cortex-A9×2、666MHz) SDRAM:DDR3 1GB u-boot:v2022.01 Linux Kernel: v5.15.0 RootFs:Ubuntu22.04 jammy armv7l |
SPIデバイス | 2.2インチ320×240フルカラー液晶 液晶コントローラ:ILI9328 aitendoモジュールキット |
ロジックアナライザ | Zeroplus社 LAP-C 16064 USB接続タイプ |
3.AXI-GPIOを有効化
VivadoでAXI-GPIOを設定していきます。プロジェクトは以前のLinux構築で作成したものを改変します。GPIOにはPSのGPIOもありますが、今回はPL(≒FPGA)に新たにGPIOのIPを構築したいと思います。
Xilinxから無償で使用可能なAXI-GPIOをIPに選んで使用します。
3-1. VivadoでAXI-GPIOのIPを追加
IP INTEGRATOR>Open Block Designでブロック図を開きます。何もないところを右クリックし、”Add IP”をクリックします。Serchに”gpio”と入力し、AXI GPIOをダブルクリックします。
Run Connection Automationをクリックし自動配線していきます。ウィンドウが出るので、以下のようにチェックを入れ、OKを押します。
以下のようになります。AXI Interconnectブロックが自動追加されていますが、正常です。ZynqのPSブロックのM_AXI_GP0ポート->AXI Interconnect->AXI GPIOとバスが接続されていますが、ZynqのGPポートがAXI3バスに対し、AXI GPIOがAXI4(Lite)なので、プロトコル変換が必要とのことらしいです。
続いてAXI-GPIOの設定をするので、ブロックをダブルクリックします。BoardタブのGPIOとGPIO2のうち、GPIO側のBoard InterfaceをCustomにします。
次にIP Configurationタブを開き、次のように設定します。
- All Inputs チェックなし
- All Outputs チェックなし
- GPIO Width =2
- Default Output Value すべて0
- Default Tri State Value すべてF
- Enable Dual Channel チェックなし
最後に端子名をわかりやすいように変えます。今回はdisp_ctrlに変えておきます。
最後にAXIGPIOのレジスタアドレス割当を確認します。Addres Editorタブを開きます。
先頭アドレス0x41200000から64KBが割り当てられたようですね。
3-2. 端子の紐づけ
毎度やっているので詳細手順は省略します。xdcファイルで端子制約条件を追記します。
まずは信号名の確認ですね。[1:0]disp_ctrl_tri_ioになっています。
xdcを更新します。各ビットは以下のように記述します。
- 0bit目:disp_ctrl_tri_io[0]
- 1bit目:disp_ctrl_tri_io[1]
今回はJC端子の7,8番に接続します。
回路図引用<https://digilent.com/reference/programmable-logic/zybo-z7/start>
あとはboot.binを作成し直して終了です。
4.デバイスツリー生成
デバイスツリー構築記事を参考にVivadoプラットフォームファイルから再生成します。
新たにpl.dtsiファイルが作られています。PL側にamba_plバスが作られ、
あとはDTCコンパイルして完了です。
5.動作確認
LEDを接続して確認をしていきます。
5-1. 端子定格の確認とLEDの配線
GPIOを紐付けしたPmod JC端子にLEDを接続していきます。
LEDを接続する前に端子の電流定格を確認します。
ドキュメントDS187[DC 特性および AC スイッチ特性] 表10 PL の I/O レベル
https://docs.xilinx.com/v/u/ja-JP/ds187-XC7Z010-XC7Z020-Data-Sheet
xdcファイル(端子制約条件)で該当端子は”LVCMOS33″つまり3.3VのCOMS入出力です。この場合のIOL、IOHは以下の記述とあります。
HR I/O バンクでは、4、8、12、または 16mA の駆動電流をサポート しています。
HR I/OバンクとはHigh Range I/Oバンクのことで3.3Vまでの広い電圧範囲に対応できる端子群です。今回使用しているGPIO端子はBank34の端子でXCZ7020の場合、HR I/Oバンクになります。
xdcファイルで駆動電流をDRIVEプロパティを設定できます。今回は記述していないのでデフォルトは12mA(おそらく)になるはずです。
LED1個程度は駆動できなくはないのですが、基本的に信号I/O用の端子で必要電流が大きいデバイスを駆動すべきではないので、電流増幅用にバッファICを外付けします。
今回は秋月で売っていた74HC245を使用します(今は非売でした)。
5-2. SysFsインターフェースによるLED点滅確認
GPIO2本でLED点滅確認をしてみます。
やり方
SysFsとは/sys/class/gpio以下のファイルを読書きすることでGPIOを制御するインターフェースです。今回追加したGPIOを有効にしてLinuxを起動すると以下のファイルが現れます。
有効前
zynq@zyboz7:~$ ls /sys/class/gpio/
.. export gpiochip904 unexport
有効後
zynq@zyboz7:~$ ls /sys/class/gpio/
.. export gpiochip1022 gpiochip904 unexport
gpiochip1022が現れました。番号は環境によって変わります。904の方はPS側のGPIOです。1022がAXI-GPIOです。
GPIOを初期化します。GPIO端子ごとに制御するためのファイル群を作成します。GPIO端子の関係は以下のようになります。
- disp_ctrl_tri_io[0] -> gpio1022
- disp_ctrl_tri_io[1] -> gpio1023
まずrootユーザに切り替えます。
$ su
次にファイル群を作成します。
root$ echo 1022 > /sys/class/gpio/export
/sys/class/gpio/gpio1022フォルダが生成します。次に入出力方向を設定します。
root$ echo out > /sys/class/gpio/gpio1022/direction
今の手順をgpio1023でも行います。次に端子出力をHigh,Lowに切り替えて手動でLED点滅をやってみます。
High出力
root$ echo 1 > /sys/class/gpio/gpio1022/value
Low出力
root$ echo 0 > /sys/class/gpio/gpio1023/value
5-3. PythonでLED点滅
PythonでLED点滅をやってみました。gif変換のやり方が悪くて0.5倍速位になってますが。
実行する際はあらかじめGPIOの初期化と入出力方向をやっておく必要があります。また、sudo実行が必要です。
Pythonではファイルの書き込みバッファなし(buffering=0)でopenするか、write()のあとにflush()メソッドを呼ぶことをしないと、GPIOがうまく切り替わりません。また、バッファなしにする場合はバイナリモードのみでテキストモードは使用不可です。
6.【補足】DockerコンテナからGPIOを使う
GPIOのインターフェースはSysfsなので、docker run時に -vオプションで共有フォルダを作り/sys以下の任意の場所をコンテナと共有します。
イメージ=debian/sampleの場合、以下のようになります。
$ docker run -v "/sys:/sys" --name "py-gpio-app" -it "debian/sample" /bin/bash