はじめに
ZephyrでI2Cの制御を試す為に購入したLCDディスプレイの制御方法について記述します。購入したLCDコントローラとI2Cの接続方法が少々特殊な為、実装の大部分がそこへの対応処理となっています。
システム概要
Block Design
以下は実装するBlock Designです。
周辺デバイスは以前の記事の実装にI2Cコントローラーを追加した形になっています。
I2Cの信号線はBASYS3のPMOD-Bに接続します。
I2Cのピンアサイン
PMOD-Bのピンアサインは以下の通りです。
今回の実装では以下の4ピンをLCDモジュールに接続します。
JB3 - SCL
JB4 - SDA
JB5 - GND
JB6 - PWR
接続用ケーブルは購入したパッケージに含まれていた物を利用しています。
開発環境
Board
BASYS3
LCD Display
Freenove I2C IIC LCD 2004年モジュール
Amazonで購入
Tools
Vivado 2025.1
Vitis 2025.1
Zephyr RTOS
Base Version - 3.7.0
Xilinx Repository Tag - 2025.1
Host OS
Ubuntu 24.04 Desktop
今回の作業でVitisは使いませんが、System Device Treeを生成する際にVitisに含まれるツールやデータを利用する為、Vitisのインストールが必要です。
Vivado/Vitis
これらのツールは/tools/Xilinx/2025.1/配下にインストールされている事を前提とします。
Vivadoのプロジェクトはスクリプトにより$HOME/ws/vivado/basys3-mbv32-i2c/配下に作成されます。
Zephyr RTOS
Zephyr RTOSはZephyr ProjectがLTS版としてリリースしている3.7.0にAMDが提供するソースを組み込んだ環境でビルドします。
本記事では$HOME/zephyrproject/配下にインストールされている事を前提とします。
作業手順
前準備
Zephyr Getting Started Guideに記載されている通り、最初はZephyr 3.7.0 Getting Started Guideの手順を実行してビルド環境を用意して下さい。
Install the Zephyr SDKに記載されている手順まで実行します。最後に記述されているudev rulesのインストールは実行しなくても問題ありません。
次に作業に利用するファイル名、ディレクトリ名を定義します。作業環境に合わせて修正して下さい。
# parameters
PROJ_NAME=basys3-mbv32-i2c
XSA_FILE=$HOME/ws/vivado/$PROJ_NAME/$PROJ_NAME.xsa
SDT_DIR=$HOME/${PROJ_NAME}_sdt
オリジナルのソースツリーにBASYS3固有のソースツリーを追加します。
最初にpythonのvenvを有効化します。インストール手順の実行直後や実行済みの場合は不要です。
source $HOME/zephyrproject/.venv/bin/activate
次にMicroBlaze-V固有のソースツリーを追加します。
cd $HOME/zephyrproject/
mv zephyr zephyr.upstream
git clone https://github.com/Xilinx/zephyr-amd.git -b xlnx_rel_v2025.1 zephyr
west update
west lopper-install
サンプルコード展開
Zephyr及びVivado Block Designを作成するコード一式をGitHubから入手して下さい。
ソースコードは$HOME/zephyrproject/zephyr/apps/配下に展開します。
mkdir $HOME/zephyrproject/zephyr/apps
cd $HOME/zephyrproject/zephyr/apps
git clone https://github.com/hkato-sssl/mbv32-zephyr-i2c-lcd.git i2c-lcd
リポジトリ名が長いのでディレクトリ名はi2c-lcdとします。
VivadoによるXSAファイル作成
展開したソースのvivadoディレクトリ配下にBlock Design作成用のスクリプトがあります。下記コマンドを実行するとプロジェクト作成からXSAファイルの作成まで実行されます。
source /tools/Xilinx/2025.1/Vivado/settings64.sh
cd $HOME/zephyrproject/zephyr/apps/i2c-lcd/vivado
vivado -mode batch -source basys3-mbv32-i2c.tcl
VivadoにBASYS3のボード情報が無い場合、ボード情報のダウンロードで多少時間が掛かります。
コマンドはテキスト・モードで実行されます。vivadoのコマンド引数を下記の様にすると、GUIで実行状況を確認できます。
vivado -source basys3-mbv32-i2c.tcl
スクリプトの実行が正常終了するとプロジェクト配下にXSAファイルが生成されます。
Devicetree更新
プロジェクト用DevicetreeをXSAファイルから抽出したDevicetreeで更新します。
# create the SDT files
. /tools/Xilinx/2025.1/Vivado/settings64.sh
xsct -eval "sdtgen set_dt_param -dir $SDT_DIR -xsa $XSA_FILE ; sdtgen generate_sdt"
# update the devicetree for Zephyr Project
cd $HOME/zephyrproject/
LOPPER_DTC_FLAGS="-b 0 -@" west lopper-command -p microblaze_riscv_0 -s $SDT_DIR/system-top.dts -w $HOME/zephyrproject/zephyr
サンプルコードのビルドと実行
ビルド手順は以下の通りです。
cd $HOME/zephyrproject/zephyr/apps/i2c-lcd
west build -p -b mbv32 .
次にHostPCとTarget BoardをUSBで接続した後、下記コマンドを実行します。
cd $HOME/zephyrproject/zephyr/apps/i2c-lcd
west flash --runner xsdb --elf-file ./build/zephyr/zephyr.elf --bitstream $SDT_DIR/$PROJ_NAME.bit
実行に成功するとシリアル上にSHELLの入力プロンプトが表示されます。
*** Booting Zephyr OS build xilinx_v2024.2-111-g986cf11c05ea ***
uart:~$
プロンプトが表示されたら以下のコマンドを入力してみて下さい。
lcd init
lcd str "Hello World !!"
lcd lf
lcd str "Zephyr is running."
lcd lf
正常に実行されれば写真の様に画面表示されます。
ShellによるLCD制御コマンド
今回の実装ではShellにlcdコマンドを追加しており、コマンドプロンプトから実行可能です。
各コマンドはcmd_lcd.cで定義しています。
lcd init - 初期化処理
Syntax:
lcd init
Description:
LCD Displayの初期化処理を実行します。立ち上げ後は必ず実行して下さい。初期化が実行されると表示がクリアされて画面左上にカーソルが表示されます。
lcd str - 文字列出力
Syntax:
lcd str <文字列>
Description:
引数の文字列をカーソル位置から表示します。ダブルクォーテーションで囲めば空白も指定可能です。文字列が画面右端まで表示されると、次の行の左端から残りの文字列が表示されます。
lcd clear - 画面消去
Syntax:
lcd clear
Description:
全画面を消去してカーソルを画面左上に移動します。
lcd cr - カーソルを行頭へ移動
Syntax:
lcd cr
Description:
カーソル位置を現在行の先頭(左端)に移動します。同じ行に表示する場合に利用します。
lcd lf - 改行
Syntax:
lcd lf
Description:
改行してカーソルを行頭に移動します。画面最下行で実行した場合、カーソルは画面上部に移動します。
lcd cursor - カーソル設定
Syntax:
lcd cursor [on | off]
Description:
引数にonまたはoffを指定してカーソル表示を設定します。
LCD制御
LCDコントローラー仕様
どうやら今回利用するLCDモジュールは昔日立が製作したHD44780というコントローラー互換製品を利用している様です。ベンダーが提供しているドライバのソースコードのコメントにHD44780というワードが見られました。
HD44780で検索すると日本語による情報が見つけられます。これらの情報とベンダ提供のZIPファイルに含まれるLCD1602.pdfを参照すればコントローラーの仕様を確認できます。
I2CによるLCD制御方法
LCDコントローラーは制御命令やデータを送る8-bit Bus I/Fと各種制御信号を持ちます。これ自体にI2Cの接続機能はありません。LCDコントローラーが持つ信号線やBusの制御はPCF8574というI/O Expanderを使います。これはI2C接続のGPIOみたいな物で、このチップを経由してLCDコントローラーの信号線を制御します。
接続イメージをベンダーの公開資料から引用します。
引用元
URL:
https://github.com/Freenove/Freenove_LCD_Module/archive/refs/heads/main.zip
File:
Freenove_LCD_Module-main/Freenove_LCD_Module_for_Arduino/Tutorial.pdf
Page 2
本記事のLCDドライバはPCF8574のP0-P7の出力状態をI2C経由で制御します。接続図ではデータ線がP7-P4の4-bitしか接続されていません。LCDコントローラーには4-bit modeという動作モードがあり、今回利用する製品は4-bit modeで制御します。
PCF8574の制御
PCF8574から出力されているP0-P7の出力状態はI2Cで8-bitデータを送信する事で設定可能です。
I2Cでデータ送信を行うには送信先デバイスのアドレスを指定する必要があります。このアドレスは引用元のZIPファイルに含まれているAbout_I2C_Address.pdfで確認できます。
P0-P7の設定処理はexpander_write()で実装しています。この関数内でZephyrのI2C APIを利用しています。
制御対象のI2Cデバイスはlcd_drv.cの下記部分で定義しています。
#define I2C_TARGET_ADDR 0x27
#define I2C_TARGET_NODE DT_NODELABEL(axi_iic_0)
static const struct device *lcd_i2c_dev = DEVICE_DT_GET(I2C_TARGET_NODE);
Backlight制御
BacklightはPCF8574のP3に接続されており、P3の設定により点灯・消灯が制御可能です。Backlight制御を行いたい場合は、このP3の値を制御します。
今回実装したドライバではP3は常にHIGHに設定している為、Backlightは常時点灯しています。
InstructionとData
ドライバはLCDコントローラーにInstruction(命令)を送信して各種制御を行い、Data(文字コード)を送信して画面上に文字列を表示します。Instructionの送信処理はwrite_instruction()、Dataの送信処理はwrite_data()で実装しています。
画面表示とカーソール位置の管理
LCDコントローラーはエディタやターミナルと同様にカーソル位置から文字列の出力を行います。このカーソル位置はコントローラー内部で管理されますが、今回実装したドライバではソフト側でもカーソル位置を管理しています。これは4行表示に対応する為です。
本記事では4行表示のLCDモジュールを利用しますが、LCDコントローラー自体には2行分の出力機能しかありません。その為、1行を半分に分けて2行として表示する事で4行表示を実現しています。
実際のLCD上では以下の順番で4行分の情報が表示されます。
| 1行目の前半部分 |
| 2行目の前半部分 |
| 1行目の後半部分 |
| 2行目の後半部分 |
この順番だと文字列表示が1行目を超えると、画面上の3行目から続きが表示されてしまいます。これを避ける為、ソフト側でもカーソル位置を管理しておき、1行目の表示が終えた時点でカーソル位置を2行目の先頭に移動します。この様に表示する行が変更されるタイミングで適切な位置に補正しています。
カーソルの位置情報はlcd_xとlcd_yで管理しておき、改行を行うタイミングでupdate_cursor_position()を実行してコントローラ側のカーソル位置を補正しています。
カーソル位置の設定にはSet DDRAM Address命令を利用します。
4-bit mode初期化処理
先に記述した通り、今回はLCDコントローラーを4-bit modeで制御しますので、先ず最初に4-bit modeに設定する必要があります。これは下記ページの情報を参考にさせて頂きました。
参考情報:キャラクタLCDモジュールの使いかた
上記ページの図4.HD44780の初期化手順に具体的な初期化手順が記述されています。今回はこの図を参考に初期化処理を実装しました。
LCDモジュールの初期化処理はlcd_init()で実装しています。4-bit modeへの設定処理はconfigure_4bit_mode()にまとめてあります。
全画面クリア
この機能はlcd_clear()で実装しています。これはClear display命令をLCDコントローラに送るだけです。命令コードの送信はwrite_insruction()で実行します。
1文字出力処理
出力の基本となる1文字出力関数はlcd_putc()で実装しています。この関数内では1文字出力の他に、CRコードやLFコードによるカーソル移動処理や、改行によるカーソル移動処理が実装されています。
文字列出力処理
これはlcd_write_string()で実装されています。この関数はlcd_putc()をループで呼び出しているだけです。
カーソル表示のON/OFF
デフォルトではカーソルが表示されますがlcd_display_cursor()を使う事で表示のON/OFFを切り替える事が出来ます。
この機能はDisplay on/off control命令を実行します。この命令には3つの機能があります。
- Display全体のON/OFF
- カーソル表示のON/OFF
- カーソル形状の選択(点滅 or アンダーバー)
lcd_drv.cではカーソル形状はアンダーバー固定としており、2番目の機能のみ利用しています。
おわりに
ZephyrのI2C APIを試す目的でLCDモジュールの制御を行いましたが、LCD制御が思いの外複雑だった為、それらの実装に手間が掛かる結果となりました。
本来の目的だったI2Cの利用ですが、こちらはDevicetreeの記述を間違えなければ利用可能です。BASYS3の場合はXSAファイルからDevicetreeが自動生成されるので簡単でした。




