はじめに
前の記事で作成したBitstreamを使い、ZephyrでSeven Segment Display(以下、7-seg displayと記述)の制御を実行します。制御はGPIOと周期スレッドを利用する形で行います。
今回実装するドライバではZephyrRTOSのThread, Timer, Mutexを利用しています。
Seven Segment Display制御方法
先ずは7-seg Displayの制御方法を簡単に説明します。下図はBASYS3のReference Manualからの引用です。
AN0-AN3で表示する桁を指定して、CA-CG,DPで表示内容を設定します。このAN0-AN3の選択とCA-CG,DPの設定を高速で切り替える事により、桁毎に異なる表示を行います。これは一般的にダイナミック点灯方式と言われる方法です。
今回のBlockDesignでは各ピンがGPIOに接続されており、点灯したい信号を0に設定する事で表示を行います。例えば、一番右端の桁を表示する場合は、AN0を0に設定すると同時にCA-CG,DPを設定します。これをAN0, AN1, AN2, AN3, AN0, ...と高速に切り替えながら表示したい内容をCA-CG,DPに設定して行きます。
サンプルコードによる制御
GPIO Driverと周期スレッドを使い7-seg Displayの制御を行うサンプルコードのビルドと実行を行います。
前提条件
開発環境は前の記事と一緒です。本記事ではDevicetree更新までの手順が実行済みである事を前提としています。
前準備
本記事で利用する共通の環境変数の定義と、westコマンドを利用可能にする為の準備を行います。
# parameters
PROJ_NAME=basys3-mbv32
XSA_FILE=$HOME/ws/vivado/$PROJ_NAME/$PROJ_NAME.xsa
SDT_DIR=$HOME/${PROJ_NAME}_sdt
# vivado
source /tools/Xilinx/2025.1/Vivado/settings64.sh
# zephyr development environment
source $HOME/zephyrproject/.venv/bin/activate
サンプルコードの取得とビルド
プロジェクトはzephyrproject/zephyer/apps配下に作成します。
最初にGitHubで公開しているサンプルコードを取得します。
mkdir -p $HOME/zephyrproject/zephyr/apps
cd $HOME/zephyrproject/zephyr/apps
git clone https://github.com/hkato-sssl/qiita-mbv32-zephyr-7seg.git 7seg
リポジトリ名が長いので、ここでは7segという名称でcloneを実行しています。
次にビルドを実行します。
cd $HOME/zephyrproject/zephyr/apps/7seg
west build -p -b mbv32
サンプルコード実行
ビルドしたディレクトリ配下で下記コマンドを実行します。
cd $HOME/zephyrproject/zephyr/apps/7seg
west flash --runner xsdb --elf-file ./build/zephyr/zephyr.elf --bitstream $SDT_DIR/$PROJ_NAME.bit
コマンド実行が成功すると0000からカウントアップする値が7-seg displayに表示されます。値のカウントアップは5ms周期で行われ、16進数で表示されます。この時、数値右下にあるドットは0.5秒間隔で移動します。
サンプルコードの説明
ファイル構成
ソースファイルはsrcフォルダ配下にあります。
- main.c
-
メイン・スレッドの実装。
一定周期でカウンタ値やドットの位置を変更する。 - seven_seg_display.c/seven_seg_display.h
-
7-seg displayの表示を行うドライバとAPIを実装。
ドライバはAPIで設定された内容を表示する。
実装概要
main.c
7-seg displayで表示する値を5ms周期でカウントアップします。カウントアップした値はAPIを使いドライバに渡します。
ファイル名はmain.cですがmain関数は定義していません。
ZephyrRTOSではmain関数の定義は必須ではありません。今回のサンプルコードではスレッドや他のリソースを全てマクロで定義しています。プログラムが起動するとマクロで定義したスレッドが自動的に起動されます。
main.cではファイルの最後尾に記述しているK_THREAD_DEFINE()マクロでスレッドを定義しています。
seven_seg_display.c
概要
7-seg display driver本体です。
ドライバは周期スレッドで7-seg displayで表示する桁を順次切り替えています。スレッドの起床周期は4msです。ドライバで利用するスレッドとタイマーはマクロで定義してあります。
ドライバもmain.c同様に全てのリソースをマクロで定義しています。その為、ドライバを初期化するAPIはありません。
APIとドライバ間の排他制御
ユーザーから呼び出されるAPIとドライバの双方でdisplay_pointsとdisplay_digitsを参照・操作します。これらの変数に対する処理の競合を避ける為にMutexを利用しています。
現状の実装では排他制御なしでも問題ありませんが、あえて行儀の良い形で実装しています。
GPIO制御
seven_seg_thread_entry()内のforループで周期的に7-seg displayの表示処理を実行しています。
for (;;) {
for (int col = 0; col < 4; ++col) {
k_timer_status_sync(&seven_seg_timer);
display_column(col);
}
}
1つの周期内の処理では1桁だけ表示します。表示する桁を周期毎に切り替える事で同時に表示している様に見せています。
1桁の表示はdisplay_column()で行います。
static void display_column(int col)
{
uint8_t d;
uint8_t points;
uint16_t digits;
uint32_t anode;
k_mutex_lock(&seven_seg_mtx, K_FOREVER);
digits = display_digits;
points = display_points;
k_mutex_unlock(&seven_seg_mtx);
d = (digits >> (col * 4)) & 15;
d = digit_table[d];
if ((points >> col) & 1) {
d |= 0x80;
}
anode = 1 << col;
gpio_port_set_masked(gpio_anode, 0x0f, ~anode);
gpio_port_set_masked(gpio_digit, 0xff, ~d);
}
colで指定された桁の値を16進数のパターンに変換し、それをGPIOで出力します。16進数の出力パターンはdigit_table[]で定義しています。
static const uint8_t digit_table[] = {
0b00111111, 0b00000110, 0b01011011, 0b01001111, 0b01100110, 0b01101101,
0b01111101, 0b00000111, 0b01111111, 0b01101111, 0b01110111, 0b01111100,
0b00111001, 0b01011110, 0b01111001, 0b01110001,
};
変換した値は以下の様にGPIO APIを使い出力します。Basys3の7-seg displayは負論理で制御する為、設定値をAPIに渡す際は値をビット反転しています。
gpio_port_set_masked(gpio_anode, 0x0f, ~anode);
gpio_port_set_masked(gpio_digit, 0xff, ~d);
Devicetree
制御対象となるGPIOはDevcetreeの定義から指定します。
static const struct device *gpio_anode = DEVICE_DT_GET(DT_NODELABEL(axi_gpio_7seg));
static const struct device *gpio_digit = DEVICE_DT_GET(DT_NODELABEL(axi_gpio_7seg_1));
axi_gpio_7segとaxi_gpio_7seg_1はbuild/zephyr/zephyr.dtsの下記部分で定義されています。
axi_gpio_7seg: gpio@40000000 {
xlnx,all-inputs = < 0x0 >;
xlnx,tri-default = < 0xffffffff >;
xlnx,gpio2-width = < 0x8 >;
xlnx,dout-default-2 = < 0x0 >;
gpio-controller;
xlnx,all-outputs-2 = < 0x1 >;
xlnx,all-inputs-2 = < 0x0 >;
reg = < 0x40000000 0x10000 >;
xlnx,tri-default-2 = < 0xffffffff >;
xlnx,is-dual = < 0x1 >;
xlnx,dout-default = < 0xf >;
xlnx,gpio-width = < 0x4 >;
#gpio-cells = < 0x2 >;
xlnx,all-outputs = < 0x1 >;
compatible = "xlnx,xps-gpio-1.00.a";
phandle = < 0x4 >;
axi_gpio_7seg_1: gpio2 {
#gpio-cells = < 0x2 >;
gpio-controller;
compatible = "xlnx,xps-gpio-1.00.a-gpio2";
phandle = < 0x5 >;
};
};
AXI GPIOは1つのデバイスで制御チャネルを2つまで持てます。7-segのLEDを点灯する信号線は2番目のチャネルに接続しているので、axi_gpio_7seg_1という形で名称の末尾に_1が付与されています。
おわりに
本記事では7-seg displayの制御をソフトウエアで実装しました。ドライバが4ms間隔で表示する桁を切り替える事により、人の目には同時に表示されている様に見せています。
しかし実際にはチラつきが気になるレベルです。BASYS3の7-segの反応が良いという理由もありそうですが、経験上ソフトウエアによる制御ではチラつきは避けられません。
この手の処理はFPGAのロジックで実装すれば、もう少しマシな表示になると思います。しかし、今回はZephyrRTOSの勉強という事で、あえてソフトウエアで実装してみました。
