HiFive 1 のプログラムを Zephyr でビルドしてみる

(更新

Building SiFive HiFive 1 app by Zephyr OS environment.

さて。昨日の続きです。

当初は SiFive の Freedom Studio を動かしてみようと思ったのですが、調べてみると、RISC-V のためのソフトウェア開発環境がいくつか登場してきていることが分かりました。GNU の toolchain があればそれでいいんじゃないの?  というのは甘い考えで、組込系(に限りませんが)のプログラムをビルドするには、標準ライブラリやらスタートアップルーチンやらリンカ指示ファイルなど、さらには GUI で書かれたプロジェクト構成ツール、はたまたウィザード形式のプロジェクトテンプレート生成ツールなど、いろんな黒子を準備してやらないといけないのです。

例えば、多くの ARM マイコンのベンダが自社向けの GNU toolchain 用 IDE(System Workbench for STM32 とか Code Composer Studio とか)を用意しているのは、自社向けのデバイスドライバ、ライブラリを用意しているというだけの理由ではないのですね。

話を戻します。私がいくつか調べてみたところでは、ベンダに依存しなそうな開発環境としては以下のようなものがありました。

前者は、有名な GNU MCU Eclipse(旧称 GNU ARM)の環境で使用することを考えていて、いままで System Workbench for STM32 や Code Composer Studio のプロジェクト構成ツールを使っていた人であれば、比較的容易に使えそうです。しかし問題は、現在のところ、この xPack は SiFive 社の RISC-V にしか対応していないようで、今後、FPGA 用のアプリを設計する上では、現在のところあまり役に立たないように思われます。

一方 RISC-V 用の Zephyr は、特定のベンダの RISC-V 実装には縛られていないようで、FPGA 設計でも利用できそうです(たぶん)。ちなみに Zephyr というのはプロセッサアーキテクチャに依存しないリアルタイム OS のようで、いずれマルチタスクのコードを書く際にも応用ができそうです。これもちょっと気になる存在ですね。

丁寧なドキュメント(以下、Getting Started Guideと呼びます)もあったので、それに従い、まずは SiFive HiFive 1 用の Hello World を動かすところまでやってみました。しかしこれも先日と同様、ツールのバージョンの更新や freedom-e-sdk のバージョンアップに伴い、説明通りではうまく動かないこともあるようです。以下、その点に注意しながら説明したいと思います。

Zephyr のインストール

上記ドキュメントにも Zephyr のインストール方法が書かれてますが、ちょっと内容が古そうなので、本家サイトに従って(以下、Zephyr ドキュメントと呼びます)インストールします。以下のところ(セクション 6)まで実行すれば OK です。

$ cd ~/zephyrproject/zephyr
$ source zephyr-env.sh

ここで「Getting Started Guide」に戻ります。QEMU での実行は、特に難しくなく再現できるでしょう。次に、HiFive 1 上で動かしてみます。

まず、GDB と OpenOCD をインストールします。これは、Zephyr でビルドしたバイナリを HiFive のフラッシュ ROM にアップロードするのに必要です。Getting Started Guide の内容は少し古いので、先日と同様、こちらに従ってブランチ v201908-branch をインストールしたほうが良いでしょう。もちろん、QEMU や J-Link は不要です。環境変数 RISCV_OPENOCD_PATH と RISCV_PATH も忘れずに設定します。

Hello World

それでは、プログラムをビルドして、フラッシュメモリに書き込んでみましょう。「Getting Started Guide」に従います。

$ mkdir build-example
$ cd build-example
$ cmake -DBOARD=hifive1 $ZEPHYR_BASE/samples/hello_world
$ make -j $(nproc)

なお、make の -j 以下の引数はなくても大丈夫です。次にフラッシュにアップロードします。HiFive 1 を USB ケーブルで繋ぎます。

「Getting Started Guide」では Flashing という項目に openocd を個別に起動する方法が説明されていますが、私の先日のブログを読まれた方は、もっと簡単な方法があることにお気づきでしょう。はい、次のようにします。

$ cd ~/freedom-e-sdk
$ ./scripts/upload --elf (上記のパスの)/build-example/zephyr/zephyr.elf \
  --openocd $RISCV_OPENOCD_PATH/bin/openocd \
  --gdb $RISCV_PATH/bin/riscv64-unknown-elf-gdb \
  --openocd-config bsp/sifive-hifive1/openocd.cfg

簡単(?)ですね。なお、共有ライブラリ libftdi1 がない、というエラーが出ることがあるようです。そのときは、sudo apt-get install libftdi1-dev でインストールします。

なお、上記スクリプト(scripts/upload)が動かない場合は、バックグラウンドで openocd が動いていないことを確認しましょう。動いている場合には pkill -TERM openocd で終了させましょう。

うまくフラッシュに焼けると、screen /dev/ttyUSBなんとか 115200 で、次のような表示を確認できるはずです。(ボード上の赤いボタンを押します。)

*** Booting Zephyr OS build zephyr-v2.1.0-186-gd748cb61188b  ***
Hello World! hifive1

動きましたね。

簡単なプログラムを書いてみる

折角なので、自分で簡単なプログラムを書いてみました。参考になるのは、こちらです。

読んでみると、ポイントとなるのは devicetree のようです。devicetree は Linux カーネルの動的コンフィグレーション(特に組込系のプロセッサ)で有名ですが、Zephyr では同様の構文を使って、アーキテクチャやデバイス、ボードの違いを吸収するようになっています。ちょっと複雑な仕組になっていますが、こちら(Zephyr – Devicetree)が参考になると思います。

まず最初に、私が書いたコードを GitHub に上げておきます。拙いコードですが、御参考ください。ビルド方法とフラッシュ ROM に書き込む方法も書いておきました。

プログラム(src/main.c)を簡単に説明しておきます。

 3  #include <drivers/pwm.h>
 4  #include <sys/printk.h>

3行目は PWM の API を使うためのおまじないです。API リファレンスはこちらです。4行目は、printk() 関数を使うためのものです。printk() をすると、HiFive 1 では UART に出力されます。

19      if ((dev = device_get_binding(PWM_DEV)) == NULL)

デバイス型へのポインタを取得するための処理です。これは、上記 devicetree の説明サイトを読んで頂くしかなさそうです。

25          pwm_pin_set_usec(dev, PWM_CH, PERIOD_US, width);

PWM の設定をしています。PWM コントローラ dev の、ポート PWM_CH(DT_ALIAS_LED1_PWMS_CHANNEL)に対して、周期 PERIOD_US [マイクロ秒] と width [マイクロ秒] を設定しています。(ちなみに、LED1 というのは RGB LED の青色の素子です。下記の hifive1.dts を見ると分かります。)

DT_ALIAS_… というのも devicetree 絡みなのですが、SiFive HiFive 1 で利用できるペリフェラルに関しては、以下の 2つのファイルで定義されています。上は SoC デバイス FE310-G000 共通のもので、下はボード特有の設定です。

  • $ZEPHYR_BASE/dts/riscv/riscv32-fe310.dtsi
  • $ZEPHYR_BASE/boards/riscv/hifive1/hifive1.dts

後者のほうを見てみると、

(略)
        aliases {
                led0 = &led0;
                led1 = &led1;
                led2 = &led2;
                pwm-led0 = &led0;
                pwm-led1 = &led1;
                pwm-led2 = &led2;

        };

(略)
        leds {
                compatible = "pwm-leds";
                led0: led_0 {
                        pwms = <&pwm1 1 0>;
                        label = "Green LED";
                };
                led1: led_1 {
                        pwms = <&pwm1 2 0>;
                        label = "Blue LED";
                };
                led2: led_2 {
                        pwms = <&pwm1 3 0>;
                        label = "Red LED";
                };
        };
(略)

という記述がありますね。ここから、DT_ALIAS_LED1_PWMS_CONTROLLER と DT_ALIAS_LED1_PWMS_CHANNEL が使えることが分かるのですが(分かりませんよね?)、cmake コマンドを実行した後に build/zephyr/include/generated/generated_dts_board_unfixed.h というファイルを覗いてみると、使えるマクロが分かります。試しに

$ fgrep DT_ALIAS ./zephyr/include/generated/generated_dts_board_unfixed.h

としてみると参考になるでしょう。今日はここまで。

一緒に勉強しましょう!

私もまだまだ勉強中です。御不明な点や参考になるサイトなどありましたら、お知らせください。

お問い合わせを頂いた後、継続して営業活動をしたり、ニュースレター等をお送りしたりすることはございません。
御返答は 24時間以内(営業時間中)とさせて頂いております。必ず返信致しますが、時々アドレス誤りと思われる返信エラーがございます。返答が届かない場合、大変お手数ではございますが別のメールアドレスで督促頂けますと幸いです。