ワークショップ・ねぶりん

開発したものとか。

C++を使うという事は?

あれこれと考慮した結果、C++を使います。理由はC言語アセンブリ言語に関しては、既に多数の解説があるからです。

しかし悲しい事に、組み込み開発でC++を利用しようとすると、猛烈に怒り出す方々が一定数います。これも良く考えると、そもそも本記事が業務用途(費用対効果を考えてメモリ容量の削減等の制約があります)を考慮する必要はないですし、ホビー用途でそんな事を言われる筋合いもありません。

AVRは小規模なMCUである事に加え、ハードもソフトも必要ならば全て自前で揃える事が出来るので、学ぶには最適な環境だと思っています。なので本記事では、C++を使うメリットと、何故C++を使うと怒られるのかを学ぼうと思います。

そうと決まったので、Atmel Studio 7.0に含まれるC++の機能を調査した所、大まかに以下の様な結果になりました。

  • GCC 5.4.0

    • C++ 14まで対応(実験的実装であるC++ 17を除く)
    • libstdc++がない

      注:GCCの最新(開発中を除く)は、現時点(2020/03/07)で9.2.0であり、これはC++ 17まで対応(実験的実装であるC++ 20を除く)

  • avr-libc 2.0.0

    • C++用の機能対応が不十分
    • 全くメンテナンスしてないと思われる機能が存在
    • ソースavr-libc-2.0.0.tar.bz2にパッチを適用しようとすると、エラーが出る箇所がある

プログラミング言語は常に最新の仕様を使いましょう、がモットーの私としては、このままでは問題点だらけです。

  • C++ 17を使いたい
  • C++の本当に使いたい機能(type_traits等)の殆どはlibstdc++の上に成り立っている
  • avr-libcを用いずに、共通ランタイム(crtxxxx.o)等を記述する自信も時間もない

これらを解決する為に、GCC 9.2.0とそのlibstdc++を含む最新ツールチェーンを自分で作成して、このツールチェーンをAtmel Studio 7.0に読み込ませる事で対応しました。

作成プラットフォームはmsys2で行い、ここで以下のツール・ライブラリ等を作成しました。

  • winpthreads-1.0mingw-w64-v7.0.0に含まれています)
  • binutils-2.33.1
  • gcc-9.2.0AVRlibstdc++対応の改造バージョン)
  • avr-libc-2.1.02.0.0ソースへのパッチ適用を諦め、最新subversionリポジトリから取得したソースに対するlibstdc++対応の大改造バージョン)
  • gdb-8.3.1Atmel Studio 7.0で使うだけなら不要)
  • avrdude-6.3Atmel Studio 7.0で使うだけなら不要)
  • avrtestwinavrに含まれています。Atmel Studio 7.0で使うだけなら不要)

記事のコンセプトを練るのに約1年、加えてここまでの環境を構築するのに半年以上を費やす事となり、進捗が大幅に遅れました。加えて、今後も更に問題が発覚した場合、その都度ツールチェーンを再構築する必要があります。2020/03/07現在、作成したlibstdc++の全ての機能が全てのAVRで動作するかは、確認していません(というか出来ません)。また AVRで利用出来ないlibstdc++の機能をどうするか? については、libstdc++のソースを出来るだけ改変しない様に、外部で対応する事にしました。具体例として、以下の様に対応しています。

  • avr-libc
    • include/unistd.h
      • read関数(externとして宣言されているが定義されていない)

        静的インライン関数として定義し、errnoENOTSUPを設定して、-1を返す様に改造

この結果、iostreamの機能はありますが、利用しても動作しません。他には、所謂インクルードガードを追加したり、extern "C"を追加したり等です。

補足:上記ツールチェーンの作成手順を公開しても良いのですが、何も読まないで「あれが動かない!直せ!」と怒り出す方々が一定数いて、この対応に苦慮する事が容易に想像出来るので、現時点では公開する予定はありません。とりあえず、まずは読み物だけと思って頂ければ幸いです

作成したツールチェーンは、どこか1つのフォルダーに纏めてインストールしておき、このフォルダーをAtmel Studio 7.0に追加します。まずToolsメニューからOptions...を選択し、Optionsダイアログを表示します。左のペインからToolchainを選択し、右のToolchains:Atmel AVR 8-bit (CPP language)に設定します。下のFlavours:Nativeしかありませんが、Add Flavourボタンを押して、表示されるダイアログに項目を設定してAddボタンを押せば完了です。以下の例では、Package NameG++9.2.0を、Package Base PathC:\AvrToolchain\binを設定しています。

f:id:neb4nebrin:20200308173752p:plain
新しいツールチェーンを追加

これで、準備が整いました。テストしてみましょう。

新しいプロジェクトを作成します。GCC C++ Executable Projectを選択して、適当な場所に名前はHelloWorldとして作成しました。デバイスATmega328Pを使いますが、今回はSimulatorを使います。

作成したら、まず最初にプロジェクトのプロパティを設定します。ProjectメニューからHelloWorld Properties...を選択し、プロパティを表示します。左のペインからAdvancedを選択し、右のToolchain Flavour:NativeからG++9.2.0に変更します。

f:id:neb4nebrin:20200308173944p:plain
ツールチェーンの変更

同様に左のペインからToolを選択し、Selected debugger/programmerSimulatorに設定します。

f:id:neb4nebrin:20200308174156p:plain
ツールの設定

最後も同様に左のペインからToolchainを選択します。上のConfiguration:All Configurationsに変更した後、下のAVR/GNU C++ Compiler配下のMiscellaneousを選択し、Other flags:-std=gnu++17と入力します。

f:id:neb4nebrin:20200308174308p:plain
ツールチェーンの設定

これでプロパティの設定は終わりです。

次にmain.cppを、以下の様にします。

#include <avr/io.h>
#include <string>

int main(void)
{
    std::string letters{ "Hello World!" };
    for (auto letter : letters)
    {
        GPIOR0 = letter;
    }
    return 0;
}

libstdc++をリンクする為に、敢えてstd::stringを利用している所がポイントで、これをビルドします。

ビルドが完了したら、まず最初に生成されたマップファイルを調べます。上から順に内容を確認していくと、以下の様に出力されています。

Archive member included to satisfy reference by file (symbol)

C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libstdc++.a(del_op.o)
                              main.o (operator delete(void*))
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_exit.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5/crtatmega328p.o (exit)
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_copy_data.o)
                              main.o (__do_copy_data)
C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libc.a(malloc.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libstdc++.a(del_op.o) (free)
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_clear_bss.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libc.a(malloc.o) (__do_clear_bss)

ATmega328Pavr5に属するので、正しい各種ランタイムが選択されている事が判ります。

次は実行してみます。GPIOR0 = letter;の行にブレークポイントを設定し、GPIOR0の値の変化を確認した所、正しく文字列Hello World!の先頭'H'から末尾'!'std::stringではC文字列の終端文字'\0'を利用しません)まで順に出力されている事が判ります。

どうやら、各種ツールチェーンの作成結果は良さそうに見えますので、次回からはこの環境を用いて、色々と試してみたいと思います。

2020/03/08


目次はこちら