MinGW でビルドする RX マイコン用 gcc

開発環境を MinGW に移行して、懸案だった RX マイコン用 gcc の構築を行った。

以前の cygwin 環境では、途中で、gcc が止まったり、妙なエラーが出て、上手くコンパイル出来ない状態だった・・
これは、適切なオプションを選択する事で回避出来るようだが、情報が無いし、試行錯誤に疲れて棚上げ状態だった。

MinGW 環境では、何とも普通にコンパイル出来るので、逆に不思議でさえ思ったが、これが普通なんだろうね・・・

コンパイルの詳細は、「Interface 2014年2月号」に詳しく載っているようだが、ネットにあるクロスコンパイラの構築などを参考にしても良いだろう。

手順が複雑で、扱うパッケージのバージョンとの相性などがある為、上手くいかない場合があると思う。

-----

RX マイコン用では、gcc-4.7.3 が良いようだ、C++11 を本格利用は出来ないが、C++0x は使えるので、問題無いと思う、gcc-4.8.x は失敗するようだ。

RX-gcc-4.7.3-ELF パッケージ
※コンパイル済みバイナリーを置いておく。(107MB)
※gcc-4.8.1 で構築した。

C 言語よりお得な C++ その9

以前に、文字列を渡す場合に、「const char*」と、「const std::string&」などの参照コンテナで渡すのと、どちらを選択すべきか書いた。

C++ はオブジェクト指向言語であり、ポインターを渡すより、コンテナを参照渡しする方が殆どの場合有利なのは明白なのだが、C から移って来た場合、オブジェクト指向プログラムに不慣れな場合もあり、中途半端な設計(ポインターだったり、コンテナだったり)になってしまう事がある。
※思い返してみると、これは自分もそうだった・・・

今回、同じような事例として、「x、y軸」の位置を渡す方法を考えてみたい。

たとえば・・・

void set(int x, int y) { }

のような関数がある場合・・・

struct xy {
    int x;
    int y;
};
void set(const xy& p) { }

このように、「x、y軸」をコンテナに入れて、参照渡しにする方が何かと都合が良い。

しかし、多くの場合、直で値を入れたい場合は、冗長では?
みたいな意見もあるのだが、通常、直で値を入れて呼ぶ事は「稀」と思うけど、それでもそのようなケースが多いなら、以下のようにすべきだろう。

struct xy {
    int x;
    int y;
    xy() { }
    xy(int x_, int y_) : x(x_), y(y_) { }   ///< コンストラクターを定義
};
void set(const xy& p) { }
void set(int x, int y) { set(xy(x, y)); }   ///< 直で呼べるように定義を追加

    set(100, 200);        ///< 直接指定
    set(xy(100, 200));    ///< 直で呼びたい場合、このように書ける。

ここで、構造体 xy だが、メンバーは、public になっていて、アクセサーを使わずにダイレクトにアクセスしている点を検討すべき問題として付け加えておく。
※この例では、自分の流儀でそのような仕様にしているだけで、本来は、このような単純なクラスでも、アクセサーを用意してアクセスする事が望ましいと思う、自分の裁量で、メンバーに与える影響などが少ない場合は、ダイレクトでも良いと思っている為で、このようなダイレクトなアクセス方法に注意して欲しい。
内部の変数にアクセサーでアクセスするようにする事で、安全性や最適化など得られるメリットは他に色々あるのですが、範囲が大きくなり過ぎると思うので、これは、又、別の機会に論じる事とします。

上の例では、メンバーの型は「int」でしたが、構造体 xy をテンプレートにする事で、他の型も簡単に定義できる。

typedef xy<short> s_xy;
typedef xy<int>   i_xy;
typedef xy<float> f_xy;

template<typename T>
struct xy {
    typedef T value_type;
    T x;
    T y;
    void set(T x_, T y_) { x = x_; y = y_; }
    xy() { }
    xy(T v) : x(v), y(v) { }     ///< カスタムコンストラクター(同じ値で初期化)
    xy(T x_, T y_) : x(x_), y(y_) { }    ///< カスタムコンストラクター(個別に初期化)
    xy& operator = (const s_xy& p) { set(static_cast(p.x), static_cast(p.y)); return *this; }
    xy& operator = (const i_xy& p) { set(static_cast(p.x), static_cast(p.y)); return *this; }
    xy& operator = (const f_xy& p) { set(static_cast(p.x), static_cast(p.y)); return *this; }
};

「operator = 」を使い、複数の型に対応する事で、型が違う代入をスムーズに行えるようになるが、「float -> short」や、「float -> int」は、変換出来ない場合がある為注意する必要がある。
※そのような変換が起こった場合は、例外を出すなどが必要かもしれない。

typedef T value_type;

は、テンプレートの実装では、よく使われるやり方で、元の「型」を再定義する事で、そのクラスで使われている「型」にアクセスする方法を提供する。

    s_xy pos(0);    ///< カスタムコンストラクターが定義されている為、x、y、を同じ値で初期化する事が出来る。
    for(s_xy::value_type i = pos.x; i < (pos.x + 10); ++i) {
        ...
    }

※これは、例題なので、名前空間に入れていないが、実際に使う場合には、必ず、何らかの名前空間に入れて運用する必要がある、そうしないと、クラス名が既存のクラスとぶつかってしまう事になる。

「代数」のクラスは、自分で実装しなくても、ネットを探すと、色々なソースを見つける事が出来る、自分で実験的に実装する事で、より広範囲な理解とスキルを身につける事が出来る、そうしてから、より洗練された実装を利用しても良い。
※ある程度自分で作って、利用していると、愛着も沸くし、他の同じような実装に移るのが難しくなる、しかしながら、自分で作ったクラスはメンテナンスし易く、機能を追加したり改良するのが、楽であるメリットがある。

MicroChip RN-52 Bluetooth モジュールを使ったオーディオ再生

Bluetooth オーディオで、音楽を飛ばす場合(A2DP)は、圧縮フォーマットの関係で、良い音で聞くのが物理的に困難だった。
しかし、最近になって、新しい高音質プロファイル(APT-X/AAC)などに対応する機器が出始めた。

丁度、Microchip 社が、↑のプロファイルに対応した Bluetooth モジュールの販売を始めたので、使ってみる事にした。
RN-52
※Microchip 社のオンラインストアでも、1個から購入可能、モジュールが20ドル、送料が10ドル程かかる(発送には2週間程度かかるようだ)

このモジュールの特徴は:
・APT-X/AAC プロファイルに対応
・デジタル出力を出せる
※設定を行う必要がある。
・比較的安価
・外付け部品が殆どいらない
・Bluetooth のオーディオ系のプロファイル(AVRCP など)を全てサポートしている
・認証:FCC、IC、CE、Bluetooth SIG
・言うまでも無く、手軽に、自分だけの Bluetooth 機器を作れる

バッテリー内臓で、持ち運びが出来る、小さいラジカセのような物が欲しかったので、丁度良い感じ~

昨日、モジュールが届いたので、早速、簡単な動作確認をしてみた。

最初のハードルは、モジュールに出ているピンの間隔が 1.2mm と狭い為、工夫しないと、ユニバーサル基板に乗せられない。
自分は、適当な基板に絶縁用の板(フォーレックス)を乗せ、その上にモジュールを置いて、ワイヤーをハンダ付けした。
RN-52 Board

モジュールを自力で何とかするのが厳しい人は、SparkFun のブレークアウトを利用すれば良いだろう~(44.95ドル)

しかし、1.2mm 間隔のハンダ付けは、そんなに厳しいものでは無いので、根気だけだと思うな。

モジュールが出来たら、必要な配線をして、ペアリングをしてみよう~

・POWEN(21) ---> パワースイッチ
・VDD(22) ---> 3.3V(電流は動作中は少ないけど、ピークで30mA くらいは流れるみたいだ・・)
・GND(1,18,27,44,45,46,47,48,49,50) ---> GND(接地)
・LED1(32) ---> LED 赤色(470)
・LED0(33) ---> LED 青色(47)
・AGND(39) ---> AGND(アナログ接地)
・SPKR_R-(40) ---> 右SP-
・SPKR_L-(41) ---> 左SP-
・SPKR_R+(42) ---> 右SP+
・SPKR_L+(43) ---> 左SP-
※L-、R- は独立した出力なので、共通にする事は出来ない、又、インピーダンスの低い負荷(直でスピーカー)をドライブする事も難しいと思う。
※一般的なステレオイヤホン(ステレオプラグなど、接地が共通の場合)などを繋ぐ場合には、アンプを入れる必要があると思う、ドキュメントの回路図などが参考になる。
※電源には1uF程度のパスコンを念のため入れておく。

とりあえず、これだけ接続して、パワースイッチを押せば電源が入る。
長押しすれば、赤、青の LED が交互に光って、ペアリング待ちになる。

------
以前に USB のデジタルオーディオインターフェースを作った時に、バーブラウンの 24 ビット DAC などを買ったので、それを接続しようと思う~
S/PDIF 出力を行うには、RXD、TXD にターミナルを接続して、コマンドで設定が必要みたいだ。

IMG_0575s

APT-X/AAC などに対応した、USB アダプターは、最近ようやく入手出来るようになってきた。
※ドライバーがカスで、使えないやつもあるみたいなので注意!、MM-BTUD44 は、問題無く使えている。

アイデア次第で、自分だけのオーディオ機器を気軽に自作できて、これは流行りそうだなー

IMG_0577s
ラジオデパートの4階で、1個200円で売っていたスピーカー(F70A21-5)と、アンプを繋いで鳴らしてみた、このスピーカー、200円とは思えないwww、これは、ちゃんと箱に入れれば、普通に使えそうだなー

☆Bluetooth AUDIO としては、ポケットに入れて持ち運べるタイプも欲しい、そこで、ヘッドホンでの音を確認する為、ヘッドホンアンプとしては最強との呼び声の TI の TPA6120A2を繋いで視聴してみたー
RN-52 内臓の D/A コンバーターもそんなに悪く無い感じだ。
※TPA6120A2 は、+-両電源が必要なので、DC/DC(MAU106) コンバーターを使っている。
※一般に売っているタイプはスティックタイプで、凄く小さいけど、電池はあまり長持ちしないし、あそこまで小さい必要は感じないので、パワフルなアンプを入れてガッツリ作りたい。
やっぱりボリュームが欲しいなぁー
IMG_0579s

C 言語よりお得な C++ その8

「printf」の功罪と「iostream」

C++ 初心者の頃 C 言語から C++ に移行して、大きな驚きを感じたのは、文字の入出力関係でしょうか?

現在 C++ でプログラムを作成するのが「常」となり、結論から言わせてもらうと、C++ では、もはや「printf」を使う理由は全く無い事です。

今「え?」と言った人は、C++ にまだ移行できていない中途半端な状態だと思われます。

    printf("Hello !!!\n");

では無くて、

    std::cout << "Hello !!!" << std::endl;

です!

C++ に馴染みの無い人には、今まで観た事の無いような、記述で、凄く奇妙に写ると思います。
C++ ではオペレーターをオーバーロード出来る為、「<<」を「シフト」では無く、全く別の意味で使う事が出来ます。
「iostream」クラスでは、cout(stdout)オブジェクトに対して、文字列を流し込む事で、文字の表示を行う事が出来るような設計になっています。
※入力は、std::cin オブジェクトから「>>」で行えます。

以前にとあるプログラマーが「iostream はわかりずらいので使う価値が無い」的な事を言っていましたが、甚だしい勘違いです。
「使い易い」、「使いずらい」と言う感覚は、単純に個人の「慣れ」の問題であって、その感覚だけで「扱いづらい」と結論してしまう事に危機的な危うさを感じます。
多分、iostream を設計した人々は、printf に関連する諸問題にとっくの昔に気がついていて、それを避け、尚且つ簡単に扱えるようにするにはどうしたら良いかを長い時間考えたり、ディスカッションして、現在の実装になったと思います。
iostream は、printf に比べると速度が遅い(負荷が大きい)と言うのはあるかもしれませんが、printf に比べると、安全で、プログラマーが受けられる恩恵が大きい優れた物です、それを良く理解しないままにわざわざ禁止する必要は無いのです。

「どんなに注意しても、人間は間違いを犯す」と言う基本的な事実があります。
printf 内のフォーマットと、可変長の引数の「型」の整合性は、コンパイラではチェックに限界があり、極めて深刻な問題をそのままエラー無くコンパイルする事が可能で、その場合、スタックを破壊する事で、微妙で見つけにくいバグをアプリケーションの動作に取り込んでしまう事があります。

iostream であれば、うっかりミスは、コンパイラがエラーを出しますし、スタックを壊すような危険な事もありません。

それでも、尚、printf の使い易さが忘れられない人には、「boost/format.hpp」を使って下さい。
これなら、ほとんど printf のような感覚で、しかも、安全に運用する事が出来ます。
※フォーマットと、引数の型の誤りには「例外」がスローされます。

文字列から数値、数値から文字列の変換には、「boost/lexical_cast.hpp」
と言うのがあり、非常に便利です、これがあれば、scanf を使う必要もありません。
※変換の失敗には「bad_lexical_cast 例外」がスローされるので、それをキャッチして、変換の失敗に備える必要がある。


これはゲームの開発での話ですが、ほとんど何処の会社でも printf を使う事を禁止しています、それは、どんなに注意しても、ミスを無くす事は出来ない為、見つける事が困難な問題をシステムに混入させてしまう危険を避ける為です。
※通常、リリースビルドでは #define で printf、sprintf などをオーバーロードして、命令を無効にするようなマクロが組まれています。

ただ、組み込みでは、残念な事があります、gcc の stdc++ ライブラリーでは、iostream クラスは他との依存が大きく巨大で、リンクすると、通常数百キロバイトのプログラムメモリーと、数十キロバイトのワークエリアを消費してしまいます、これでは、少ないリソースのマイコンでは物理的に使えません。
そこで、もし必要なら、小規模な iostream クラスを作成する必要があります(これは車輪の再発明ではありません)、ホビーで使うのであっても、あくまでも、安全性と利便性から、printf を使わない決断をすべき事項だと思います。
※以前、C 言語の時代でも、printf が巨大な為、多くの人が、tiny printf のような物を作って使っていました。
※オペレーターや、オブジェクト指向の勉強にもなるので、自分で作ってみると楽しいかもしれません。

一応、私が実装した、クラスを紹介します。
※これは、出力先として、「void sci_put(char);」、「void sci_puts(const char*);」などのシリアルインターフェースを使っています。

namespace utils {
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  chout クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    struct chout {
    static const char endl = '\n';

    private:
    char        sup_ch_;
    char        hex_ch_;
    uint8_t     len_;

    public:
        //-----------------------------------------------------------------//
        /*!
            @brief  コンストラクター
        @param[in]  out 文字出力関数
        */
        //-----------------------------------------------------------------//
     chout() : sup_ch_('0'), hex_ch_('a'), len_(0) { }

        //-----------------------------------------------------------------//
        /*!
            @brief  16 進表示の英数字を大文字にする
        @param[in]  cap 「false」を指定すると小文字
        */
        //-----------------------------------------------------------------//
    void hexa_decimal_capital(bool cap = true) {
        if(cap) hex_ch_ = 'A'; else hex_ch_ = 'a';
    }

        //-----------------------------------------------------------------//
        /*!
            @brief  表示文字数を指定
        @param[in]  len 表示文字数
        */
        //-----------------------------------------------------------------//
    void set_length(uint8_t len) { len_ = len; }

        //-----------------------------------------------------------------//
        /*!
            @brief  ゼロサプレス時の文字を指定
        @param[in]  ch  文字
        */
        //-----------------------------------------------------------------//
    void suppress_char(char ch) { sup_ch_ = ch; }

        //-----------------------------------------------------------------//
        /*!
            @brief  文字表示
        @param[in]  ch  文字
        */
        //-----------------------------------------------------------------//
    void put(char ch) const { sci_putch(ch); }

        //-----------------------------------------------------------------//
        /*!
            @brief  文字列の表示
        @param[in]  str 文字列
        */
        //-----------------------------------------------------------------//
    void string(const char* str) const { sci_puts(str); }

        //-----------------------------------------------------------------//
        /*!
            @brief  長さ指定文字列表示
        @param[in]  str 文字列
        @param[in]  len 長さ
        */
        //-----------------------------------------------------------------//
    void len_string(const char* str, uint8_t len) const {
        while(len_ > len) {
            put(sup_ch_);
        ++len;
        }
        string(str);
    }

        //-----------------------------------------------------------------//
        /*!
            @brief  16 進数の表示
        @param[in]  val 値
        */
        //-----------------------------------------------------------------//
    uint16_t hexa_decimal(uint32_t val) const {
        char tmp[8 + 1];
        uint16_t pos = sizeof(tmp);
        --pos;
        tmp[pos] = 0;
        do {
        --pos;
        char n = val & 15;
        if(n > 9) tmp[pos] = hex_ch_ - 10 + n;
        else tmp[pos] = '0' + n;
        val >>= 4;
        } while(val != 0) ;

        len_string(&tmp[pos], sizeof(tmp) - pos - 1);
        return sizeof(tmp) - pos - 1;
    }

        //-----------------------------------------------------------------//
        /*!
            @brief  10 進数の表示
        @param[in]  val 値
        @param[in]  minus   マイナス符号を表示する場合「true」
        @return 表示文字数
        */
        //-----------------------------------------------------------------//
    uint16_t decimal(uint32_t val, bool minus = false) const {
        char tmp[11 + 1];
        uint16_t pos = sizeof(tmp);
        --pos;
        tmp[pos] = 0;
        do {
            --pos;
        tmp[pos] = '0' + (val % 10);
        val /= 10;
        } while(val != 0) ;

        if(minus) {
        --pos;
        tmp[pos] = '-';
        }

        len_string(&tmp[pos], sizeof(tmp) - pos - 1);
        return sizeof(tmp) - pos - 1;
    }

        //-----------------------------------------------------------------//
        /*!
            @brief  符号付き 10 進数の表示
        @param[in]  val 値
        @return 表示文字数
        */
        //-----------------------------------------------------------------//
    uint16_t decimal(int32_t val) const {
        bool minus = false;
        if(val < 0) {
            minus = true;
        val = -val;
        }
        return decimal(static_cast(val), minus);
    }

    chout& operator << (const uint32_t val) {
        decimal(val);
        return *this;
    }

    chout& operator << (const int32_t val) {
        decimal(val);
        return *this;
    }

    chout& operator << (const char* str) {
        string(str);
        return *this;
    }

    chout& operator << (const char ch) {
        put(ch);
        return *this;
    }
    };
}

※ただ、このクラスでは、浮動小数点や、2進数、8進数の表示をサポートしていません、浮動小数点表示が出来ないのは痛いので、改修する予定です。

現在、独自の format クラス一式を実装してあります、GitHUB 参照下さい。

MinGW による GLFW3、FreeType2、Bullet 環境の構築

今まで、Windows では主に cygwin64 を使って開発環境を整えていた。
しかし、ここにきて、色々な不具合に遭遇する・・・

たとえば、組み込みマイコンのクロス開発環境として、gcc をビルドするのだけど、cygwin では途中で必ず失敗する。
※cygwin では、何か特別なオプションを追加するのかもしれないが、情報が無いし、原因を追って、試すのに時間が掛かり過ぎる。
※大抵は、gcc がコンパイルエラーで止まる。
また、少し大きなライブラリー(bullet physics)をコンパイルしようとした場合にスタック不足で gcc-w64-mingw32-gcc がクラッシュしたり、リンク中に止まったりと、非常に辛い状況が連発していた。
状況を改善する為に、ソースを追ったり、オプションを追加したり、情報を求めて時間を浪費したものの、良い改善策が見つからない、これは、cygwin のバージョンアップにより解消するのかも知れないと思ったりもしたが、もう疲れた・・・
そこで、別の解決策として、MinGW 環境を試してみる事にした。

MinGW は、よりコンパクトに必要最小限のコマンドを集約した、gcc を使った Windows の開発環境と思えば良いと思う。
MinGW には MinGW の流儀(常識)があり、それを学ぶ必要がある為、時間がかかるので、躊躇していたが、使ってみない事には判らない。

それで、とりあえず、インストーラーを使ってインストールしてみた。

しかし、入れたハズの gcc すら動かない!?
何で?、と思って数日、profile のパスにバグがあり、それが原因だったようだ。

/c/MinGW/msys/1.0/etc/profile
の19、21行目
/mingw/bin:/bin ---> /c/mingw/bin:/bin
ドライブレターが抜けているようだが、正規のインストーラーでインストールしてこの完成度は「痛い」としか言い様が無い、ホント大丈夫なの?

気を取り直して、コマンド窓を、評判の mintty にしてみた、これは簡単、情報も多く直ぐに導入出来た。

自分は emacs 使いなので、コンソールで使える emacs を使いたかったが、MinGW では標準では無いので、仕方なく、Windows の emacs を入れて、パスを通した。

ここまで出来て、判った事。

・MinGW では、コンソールで emacs を動かすのは難しいようだ。(vim は標準であるが、キーバインドの慣れもあるので自分には使いづらい)
・Windows の日本語ファイル名などをスマートに解決出来ないようだ。(色々な理由で UTF-8 を使いたいが、それだと色々破綻する、とはいえ、CP932(Shift-JIS)も駄目で、日本語ファイル名の文字化けは我慢するしか無いようだ。
・cygwin では、大抵の unix コマンドは揃っていて、インストーラーで簡単にインストール出来るが、MinGW では、標準に無い物は自分でコンパイルして導入する必要があり、簡単に導入出来るものと、パッチを当てたり、改修する必要がある物など、簡単では無い場合があるようだ。

まず、自分のフレームワークで使っているライブラリーをコンパイルする必要がある。
「configure、make」で解決できるものと、「cmake」を使う物があるみたいなので、cmake の windows 版をインストールして、パスを通した。
※cmake はMinGW で動作する実行ファイルを作るのでは無く、windows 専用の物を使うようで、GUI 環境で使える版も付いてくる。

(1)標準的な各種ライブラリーのインストール

Windows 関係ライブラリー:
    mingw-get install mingw32-w32api

pthread ライブラリー:
    mingw-get install mingw32-pthreads-w32

zlib ライブラリー:(標準では DLL しか入らないようだ)
    mingw-get install mingw32-libz

※「mingw-get」で、GUI が起動するので、それで、必要そうな物はインストール出来る。

(2)GLFW-3.0.4 のコンパイル
MinGW の常識が判っていなかった為、コンパイル出来るまで試行錯誤が続いた。
glfw3 のアーカイブを解凍したら、ディレクトリーを移って。

    cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local

として作成する。
※cmake は途中で失敗したら、キャッシュが残るようで、後何をやっても駄目ななので、アーカイブからやり直した(他にもっと良い方法があると思う)

後はお決まりの・・・

    make
    make install

とりあえず、GLFW3 のコンパイルは出来た。

ここまでの感触で、自分フレームワークを MinGW に移っても問題無いように感じた。

追記:2014/1/21
(3)freetype-2.5.2 ライブラリーの構築
freetype は、TrueType のレンダリングを行うオープンソースライブラリーで、自分のフレームワークでも日本語フォントの描画で使っている。
このライブラリーのコンパイルは、少し工夫が必要だった・・
インクルードファイルの相互依存の影響で、定義がすっぽ抜ける現象が起こる、本来はもっとスマートに解決したいが、無理やりだが以下のように解決した。

「ftgip.c」に、typedef を二行追加。
    typedef long off_t;
    typedef long long off64_t;

    ./configure --without-png
※ PNG が必要なのは、TrueType ファイル内に PNG でエンコードされたフォントのビットマップがあるのだろうけど、とりあえず、無視するようにした。

    make
    make install

(4)physics bullet-2.82 のコンパイルとデモプログラムの生成
bullet は物理法則をシュミレーションする演算ライブラリーで、色々な場面で使われている。
Visual Studio でコンパイルするのが一般的なようだが、MinGW でもライブラリーを作成する事が可能なようだ。
※現状の gcc-4.8.1 では、コンパイル中にクラッシュする為、ライブラリーを作れないが、クラッシュするソースを微妙に修正するとコンパイルが正常終了する事が判った。

修正1: src/BulletCollision/CollisionDispatch % emacs btInternalEdgeUtility.cpp
内「struct btConnectivityProcessor : public btTriangleCallback」クラスで、

btVector3 calculatedEdge = edgeCrossA.cross(edgeCrossB);
を、以下のように修正
calculatedEdge = edgeCrossA.cross(edgeCrossB);

btVector3 calculatedNormalB = normalA;
を、以下のように修正
calculatedNormalB = normalA;

変数として以下の二行を追加する。
	btVector3 calculatedEdge;
	btVector3 calculatedNormalB;
※この修正、厳密には正しく無いが、多分動作するだろうと思う、正しく動作しているかの検証はしていない、bullet は発展途上で、実装があっても使われていない事もあるので、どのデモプログラムを動作した場合にこのクラスが動くか見極めていない、コンパイルが通るだけのワークアラウンドと思って欲しい。

修正2: src/BulletDynamics/Character % emacs btKinematicCharacterController.cpp
383、384 行目をコメントアウト(単純に使われていないが、あるだけで gcc がクラッシュする・・)
///     btScalar hitDistance;
///     hitDistance = (callback.m_hitPointWorld - m_currentPosition).length();

GLUT の対応:
GLUT は、MinGW 用にコンパイルすれば良いのだが、GLFW3 を使うようになって OpenGL では使わなくなった為、bullet のデモを動かす為の対応として、
Glut/GL/glut.h ---> /c/MinGW/mingw32/include/GL にコピーする。
又、ライブラリー glut32.lib を、名前を変更して、コピーする ---> /c/MinGW/mingw32/lib/libglut32.a
※VisualStudio で C 言語ソースだけでコンパイルされたライブラリーは gcc のライブラリーと互換性がある。

    cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local
    make
    make install

(5)libpng-1.6.8 ライブラリーの構築
これは、普通に問題なく構築できる

    ./configure
    make
    make install

(6)openjpeg-2.0.0 ライブラリーの構築
標準的には、シェアードライブラリー形式(DLL)で生成される為、それをOFFにしてスタティックライブラリーにする

    cmake -G "MSYS Makefiles" -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_INSTALL_PREFIX=/usr/local
    make
    make install  

(7)JPEG ライブラリーの構築
JPEG ライブラリーは、x86 CPU の場合、マルチメディア命令で最適化されたバージョンがあるので、それを使う。
Independent JPEG Group's JPEG software release 6b
with x86 SIMD extension for IJG JPEG library version 1.02

※VisualStudio の C コンパイラで作成されたライブラリーファイルは、gcc と互換性があるので、「xxx.lib」を「libxxx.a」などにリネームすれば良い。
このライブラリーは非常に高速なので、画面をキャプチャーしてそれをリアルタイムに JPEG 圧縮して出力すれば、動画にも対応できる。(モーション JPEG 的な・・)
※生の画像を出力しない方が、ファイルサイズが小さくなり、高速化する。

(8)mupdf-1.3 ライブラリーの構築
mupdf ライブラリーはオープンソースの PDF ファイルを展開してビットマップを生成するライブラリーで、PDF を簡単に開けるので、便利に使っている。
このライブラリーは、freetype、openjpeg、jpeg、jbig2dec などを利用するのだけど、各ライブラリーのバージョンの違いを吸収する為、全てのソースコードを同梱している、構築には無駄にならないように、自前のライブラリーをリンクさせる。

まず Makefile を修正

3 行目「build ?= debug」を「build ?= release」としとく。

「include Makethird」をコメントアウトしておく「# include Makethird」

LIBS に追加「LIBS += -L/usr/local/lib -lpng -ljpeg_x86 -lopenjp2 -ljbig2dec -lfreetype -lz」

CFLAGS にも追加「CFLAGS += -I/usr/local/include -I/usr/local/include/openjpeg-2.0 -I/usr/local/include/libjpeg_x86 -I/usr/local/include/freetype2」
※JPEG ライブラリーは x86 CPU 用に最適化されたバージョンを使っている。

    make
    make install

C 言語よりお得な C++ その7

前回、コンペアマッチタイマーの制御をテンプレート化してみました。

今回は、少し複雑ですが、シリアルコミュニケーションインターフェースをテンプレート化してみます。

実用的なシリアルコミュニケーションでは、通常、受信、送信は割り込みによって行い、メインとは FIFO などでやりとりします。
さらに RX マイコンでは、DMA も使う事が出来ますが、やりとりするデータ量と、出し入れに係わる細かい操作を考えると、DMA を使う事にあまりメリットが無いので、通常の割り込みで行う設計とします。

FIFO のバッファサイズは、アプリケーションの構造、送受信のボーレート、などにより最適なサイズがあると思われますので、可変に出来るようにします。
※以前のコンペアマッチタイマーより、バッファサイズをパラメーターとしている為、少し複雑です。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  SCI I/O 制御クラス
        @param[in]  SCI SCIx 定義クラス
        @param[in]  recv_size   受信バッファサイズ
        @param[in]  send+size   送信バッファサイズ
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class SCIx, uint32_t recv_size, uint32_t send_size>
    class sci_io {

        static utils::fifo<recv_size>   recv_;
        static utils::fifo<send_size>   send_;

        static INTERRUPT_FUNC void recv_task_()
        {
            bool err = false;
            if(SCIx::SSR.ORER()) {  ///< 受信オーバランエラー状態確認
                SCIx::SSR = 0x00;   ///< 受信オーバランエラークリア
                err = true;
            }
            ///< フレーミングエラー/パリティエラー状態確認
            if(SCIx::SSR() & (SCIx::SSR.FER.b() | SCIx::SSR.PER.b())) {
                err = true;
            }
            if(!err) recv_.put(SCIx::RDR());
        }

        static INTERRUPT_FUNC void send_task_()
        {
            SCIx::TDR = send_.get();
            if(send_.length() == 0) {
                SCIx::SCR.TEIE = 0;
            }
        }
...

重要な部分は、fifo の定義です、割り込み関数とクラスとで、送受信データをやりとりする必要がある為、「static」としています。
テンプレートのパラメーターから、受信サイズ、送信サイズを受け取って、静的に宣言されます。
割り込み関数も static 宣言します、この関数アドレスは、初期化時、割り込みベクターに渡されるようにしています。

    sci_io<device::SCI0, 128, 128> sci0_;
    sci_io<device::SCI1,  64, 256> sci1_;

↑のように、SCI0、SCI1 を宣言すると、テンプレートパラメーター SCIx が異なる為、static に宣言された fifo の領域は SCI0、SCI1 で別々に確保されます。

クラス内の static 宣言では実態を別に宣言しておく必要があります。

    template<class SCIx, uint32_t recv_size, uint32_t send_size>
        utils::fifo<recv_size> sci_io<SCIx, recv_size, send_size>::recv_;
    template<class SCIx, uint32_t recv_size, uint32_t send_size>
        utils::fifo<send_size> sci_io<SCIx, recv_size, send_size>::send_;

どうでしょうか、これで、チャネル毎バッファサイズを変更して静的に使う事が出来ます。

全ソースコードは github にあります。

デジタル・スイッチング・レギュレーター

現在、SW レギュレーターを組もうと思ったら、専用の IC を買えば済む、しかし、微妙に違う仕様のICが数限りなくあり、ベストな選択をする事が難しいし、数個購入するとなると、割高でもある。

とりあえずのゴールは、リチウムイオン電池の充電や、ブラシレスモーターの制御なのだが、専用 IC で組むとそれなりの値段になってしまうし、より細かい制御をしようと思うと、マイコンの助けも必要なので、1個のマイコンだけで、全ての制御を行う予定。

まず、「昇圧」は、電流の管理が必須で、誤るとドライバーを破壊するので、無難な「降圧」方式で実験してみた。

12 ビットの A/D コンバーターを使って、フィードバックを行い、指令電圧に追従させてみた。
まず、一番単純な制御で行ってみた。

IMG_0534ss

A/D チャネル0: 10K のボリューム(指令電圧)
A/D チャネル1: 出力電圧(1/6)
A/D チャネル2: 入力電圧(1/6)

A/D の入力には、AD8656 をバッファアンプに使い、1/6 に分圧して、基準電圧には 2.5V のリファレンスを使った、電源は 12V 。
※写真のボードでは、保護抵抗やリミッターを省いているが、付けた方が無難だろう。

パワー MOS-FET は、IR 社の IRLR3114 、ドライバーはリニアテクノロジーの LTC4442

LTC4442 の制御電圧は FET のゲート電圧を考えて 10V 程度を供給している。
※バイパスコンデンサをしっかり配置しないと、正常に動作しない、使うのにコツがいるようだ、ハイサイド側が ON した時、かなりハンチングしているようで、原因が良く判らない・・・
IMG_0535ss
LTC4442 は、上下の FET が貫通しないような工夫がしてあるので、デッドタイムの制御はしなくていいのでコンビニエンスだ・・(それが正しく働いてなくて、ハンチングしているのかも・・)

RX63T は、PWM タイマーの周波数として 100MHz を扱えるので、9 ビットの分解能として、187.5KHz (96MHz / 512) を実現している。
インダクターは TDK の 22uH 電圧にもよるけど、このサイズ(容量)なら 500mA 程度なら取り出せるだろうか・・

メインループは、1000Hz なので、応答は、そんなに高速では無いが、サンプリング方式では、どのみち限界がある。

ソースコード一式を、github にプッシュしてある。

-----
今後の課題として、もっと違った制御法を試して、ステップ応答などの特性を評価してみないといけない。

追記 (2014/1/1)(2014/1/2):
・比例制御から、もう少し違う制御にしてみた・・
・A/D の変換タイミングを、PWM に同期させてみた。
・ハイサイドの FET をチャージポンプで駆動している為、パルスが無くならないように、最低値と最大値を制限。
・サンプリングは 10KHz にした。

    bool up = true;
    int32_t base_gain = 651;
    int32_t high_gain = 1500;
    int32_t low_limit = 10;
    int32_t high_limit = 500;
    int32_t cpv = low_limit;
    while(1) {
        adc_.start(0b00000111);

        cmt_.sync();

        // A/D 変換開始
        adc_.sync();
//        int32_t ref = static_cast<int32_t>(adc_.get(0)); // 指令電圧
        int32_t out = static_cast<int32_t>(adc_.get(1)); // 出力電圧
        int32_t inp = static_cast<int32_t>(adc_.get(2)); // 入力電圧

        // 三角波
        if(up) {
            ref += 20;
        } else {
            ref -= 20;
        }
        if(ref > 1700) {
            up = false;
            ref = 1700;
        } else if(ref < 200) {
            up = true;
            ref = 200;
        }

        int32_t dif = ref - out;  // 誤差
        // PWM の制御量に対するスレッショルド
        if(std::abs(dif) < 40) {
            if(dif < 0) --cpv;
            else ++cpv;
        } else {
            // 基本的な制御量の計算
            int32_t d = dif * 512 / inp;

            // 指令電圧、入力電圧の比に応じて、ゲインを制御
            // ・指令電圧が低い場合はゲインを小さくする
            int32_t g = (high_gain - base_gain) * ref / inp;
            g += base_gain;
            if(d < 0) g -= g / 4;
            cpv += d * g / 4096;
        }

        // 出力リミッター
        if(cpv < low_limit) cpv = low_limit;
        else if(cpv > high_limit) cpv = high_limit;

        gpt_.set_a(cpv);
        uint16_t ofs = (512 - cpv) / 2;
        gpt_.set_ad_a(cpv + ofs);   // A/D 変換開始タイミング

↑はメインループ(サンプリング)部分
※実用的には過電流保護なども必要。
出力に 1000uF のコンデンサと 10uF のセラミックコンデンサを入れたら、リップルはかなり小さくなり、これなら実用的と思える。
※電圧が低い場合にはゲインを抑えるように改修

IMG_0537ss
※三角波を出力したところ
これなら、まぁ許容できるかも・・

追記(2014/1/7):
トップのオン時、出力が振動するのは、リニアテクノロジーの資料を読んでたら、トップ側FETに並列にショットキーダイオードを入れる事で改善する事が判り、早速入れてみた、完全には無くならないが、確かに改善してる。
全体的にリップルも減った。

DC/DC コンバーターとリチウム電池充電

16x16 ドットマトリックス LED 用の電源回路を作ってみた。

これは、一応キッチンタイマーなので、電池で動作する必要がある。

電池には、リチウムイオンポリマー電池を使ったので、5Vの昇圧と、充電回路も組み込んだ。

IMG_0528ss
IMG_0529ss

昇圧の DC/DC コンバーターは、電流が少なければ、HT7750Aとかでも良いのだけど、このICは、電流を多く取れないし、効率も悪く、フライホイールダイオードを追加する必要がある、そこで色々捜していたが、中々良い物(値段が安い)が無い、そんな時、EMH7601を見つけた!、このICは同期整流で、電流も多く取れてシンプルで高性能、値段も100円と、安い!
早速コレを使う事に決めた。
充電には、MCP73831T-2ACIを使った、これも2個で100円と格安だ。

なるべく小さいスペースに詰め込んだ為、かなりキュウキュウだけど、とりあえず、何とか動作した。
ただ、残念なのは、「スライドスイッチ」だった・・・
予定では、二回路を使い、電源 OFF 時に、USB から充電、電源 ON 時には、USB から給電する予定だったが、1回路を買ってしまい、電源 OFF 時でも充電と給電をしてしまう。
いまさら、スイッチを交換するのもシンドイので、とりあえずそのままになってる。
※電源 ON 時に、USB から給電すると、DC/DC は完全にシャットダウンされるように工夫してある、P チャネルと N チャネルのFETを使って DC/DC の SD 端子を制御している。
ゲートを1回路だけ使いたいとか、要求が良くあるので、そんなのが欲しいとこだけど手持ちに無い、調べるとマルツパーツで売ってるみたいなので、今度買っておこう~

EMH7601 は、ほんとに素晴らしい~、通常ゲートを低電圧で動かして、低 ON 抵抗で高速に動かすのは難しいのだが・・・、それに同期整流で、外にショットキーダイオードを追加しなくても良い点も素晴らしい。
こうゆう斬新な製品は、意外と知られていないメーカーが創っており、入手が難しかったりするのだが、今回は Aitendo で扱っていてラッキーだった。

充電回路と DC/DC を合わせた小さい基板は需要がありそうだから、作ればよいと思うが、作るとなるとそれなりの値段になってしまうのだろうな・・・

C 言語よりお得な C++ その6

前回、非常に簡単ではありましたが、テンプレートで 「C 言語よりお得な C++ その5」FIFO のサイズを可変する方法を書きました。

今度は、もう少し複雑ですが、もっと実用的な実装のアイディアを示します。

以前から主に RX マイコン用の I/O 定義を C++ で実装して来ました、今回それを活用して実際に I/O の操作を行うクラスをテンプレートで実装してみます。

組み込みマイコンでは、ハードウェアーリソースは、大抵複数のチャネルがあります。
例えば、RX63T には CMT(コンペアマッチタイマー)は4チャネルあります、チャネル毎に操作する I/O の対象は微妙に違い、C のプログラムでは、チャネル毎に別々に実装するしかありませんでした(define のマクロで解決する方法は論外です)、このような微妙な違いは、大抵は、I/O のアドレスが違うとか、ビットの位置が違うなどでした、ですから、自分がどのチャネルに対して操作しているか判れば、柔軟性のあるドライバーを書く事は出来るのですが、テンプレートを使うと、非常にシンプルに判りやすく書けます、また最適化によって余分な部分は自動的に消してくれますので、リソースのダイエットや速度の向上が望めます。

※少し長いですが、cmt_io.hpp を示します。

#pragma once
//=====================================================================//
/*! @file
    @brief  RX62N, RX621, RX63T グループ・CMT I/O 制御 @n
            Copyright 2013 Kunihito Hiramatsu
    @author 平松邦仁 (hira@rvf-rc45.net)
*/
//=====================================================================//
#include "cmt.hpp"
#include "rx63x/system.hpp"
#include "rx63x/icu.hpp"
#include "vect.h"

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  CMT I/O クラス
        @param[in]  CMTx    CMT チャネルクラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class CMTx>
    class cmt_io {

        uint32_t    clock_;

        void sleep_() { }

        static volatile uint32_t counter_;
        static void (*task_)();
        static INTERRUPT_FUNC void cmt_task_() {
            ++counter_;
            if(task_) (*task_)();
            switch(CMTx::get_chanel()) {
            case 0:
                ICU::IR.CMI0 = 0;
                break;
            case 1:
                ICU::IR.CMI1 = 0;
                break;
            case 2:
                ICU::IR.CMI2 = 0;
                break;
            case 3:
                ICU::IR.CMI3 = 0;
                break;
            }
        }

    public:
        //-----------------------------------------------------------------//
        /*!
            @brief  コンストラクター
        */
        //-----------------------------------------------------------------//
        cmt_io() : clock_(0) { }


        //-----------------------------------------------------------------//
        /*!
            @brief  ベースクロックの設定
            @param[in]  clock   ベース周波数
        */
        //-----------------------------------------------------------------//
        void set_clock(uint32_t clock) { clock_ = clock; }


        //-----------------------------------------------------------------//
        /*!
            @brief  初期化
            @param[in]  freq    タイマー周波数
            @param[in]  level   割り込みレベル
            @return レンジオーバーなら「false」を返す
        */
        //-----------------------------------------------------------------//
        bool initialize(uint32_t freq, uint8_t level) const {
            if(freq == 0 || clock_ == 0) return false;

            uint32_t cmcor = clock_ / freq / 8;
            uint8_t cks = 0;
            while(cmcor > 65536) {
                cmcor >>= 2;
                ++cks;
            }
            if(cks > 3 || cmcor == 0) {
                return false;
            }

            uint32_t chanel = CMTx::get_chanel();
            task_ = 0;
            switch(chanel) {
            case 0:
                set_interrupt_task(cmt_task_, ICU::VECTOR::CMI0);
                CMTx::CMSTR0.STR0 = 0;
                SYSTEM::MSTPCRA.MSTPA15 = 0;
                ICU::IPR.CMI0 = level;
                ICU::IER.CMI0 = true;
                ICU::IR.CMI0 = 0;
                break;
            case 1:
                CMTx::CMSTR0.STR1 = 0;
                SYSTEM::MSTPCRA.MSTPA15 = 0;
                set_interrupt_task(cmt_task_, ICU::VECTOR::CMI1);
                ICU::IPR.CMI1 = level;
                ICU::IER.CMI1 = true;
                ICU::IR.CMI1 = 0;
                break;
            case 2:
                CMTx::CMSTR1.STR2 = 0;
                SYSTEM::MSTPCRA.MSTPA14 = 0;
                set_interrupt_task(cmt_task_, ICU::VECTOR::CMI2);
                ICU::IPR.CMI2 = level;
                ICU::IER.CMI2 = true;
                ICU::IR.CMI2 = 0;
                break;
            case 3:
                CMTx::CMSTR1.STR3 = 0;
                SYSTEM::MSTPCRA.MSTPA14 = 0;
                set_interrupt_task(cmt_task_, ICU::VECTOR::CMI3);
                ICU::IPR.CMI3 = level;
                ICU::IER.CMI3 = true;
                ICU::IR.CMI3 = 0;
                break;
            }

            CMTx::CMCR = CMTx::CMCR.CMIE.b() | CMTx::CMCR.CKS.b(cks);
            CMTx::CMCOR = cmcor - 1;

            switch(chanel) {
            case 0:
                CMTx::CMSTR0.STR0 = 1;
                break;
            case 1:
                CMTx::CMSTR0.STR1 = 1;
                break;
            case 2:
                CMTx::CMSTR1.STR2 = 1;
                break;
            case 3:
                CMTx::CMSTR1.STR3 = 1;
                break;
            }
            return true;
        }


        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みタスクを設定
            @param[in]  task    設定タスク
        */
        //-----------------------------------------------------------------//
        void set_task(void (*task)()) const {
            cmt_task_ = task;
        }


        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みと同期
        */
        //-----------------------------------------------------------------//
        void sync() {
            volatile uint32_t cnt = counter_;
            while(cnt == counter_) ;
        }


        //-----------------------------------------------------------------//
        /*!
            @brief  割り込みカウンターの値を取得
        */
        //-----------------------------------------------------------------//
        uint32_t get_count() const {
            return counter_;
        }


        //-----------------------------------------------------------------//
        /*!
            @brief  CMT カウンターの値を取得
        */
        //-----------------------------------------------------------------//
        uint16_t get_cmt_count() const {
            return CMTx::CMCNT();
        }
    };
    template <class CMTx> volatile uint32_t cmt_io<CMTx>::counter_;
    template <class CMTx> void (*cmt_io::task_)();
}

ここで、重要なしくみは、「static」で宣言された関数と変数です、これは、割り込みルーチンと、クラスとのデータをやり取りする部分ですが、static に宣言されている為、クラスのインスタンスを取得する事が必要では無く、割り込み関数から簡単にアクセス出来ます。

※クラス外で、static 宣言されたリソースの実態を記述しておく必要がありますが、それは、コードの最後の方で行っています。

    template <class CMTx> volatile uint32_t cmt_io<CMTx>::counter_;  // カウンター変数の実態
    template <class CMTx> void (*cmt_io::task_)();  // 関数アドレスの実態

クラス内で static 宣言された変数やクラスは、そのクラスで共有されるのですが、このクラスはテンプレートクラスなので、CMT のチャネルが違えば、変数や、関数は、別々にインスタンス化されますので、好都合です。

このテンプレートクラスでは、個々の処理で、チャネル番号を取得して、switch 文により、処理を分けていますが、ここが、テンプレートの賢いところで、チャネル番号は、インスタンス化の時に定数なので、余分なケースは取り除かれてチャネル固有の実装だけが、取り込まれます。

どうでしょうか?、組み込みでも C++ を使う効果が理解できたのでは無いでしょうか?

※このソースコードや、I/O 定義ヘッダーは github にありますので参照して下さい。

WinAVR C++ の実力とは!?

この記事は、C++ Advent Calendar 2013 12月4日(水)の記事です。

前回は ボレロ村上 さんの記事でした。

最初に断っておきますが、C++ 関係の話題は、ほんの少しで、後は、少し毛色の違うマッタリとした話です。

WinAVR は、Atmel AVR Microcontrollers 用の gcc を使った C、C++ コンパイラーコレクションです。

今時、「gcc が使えるマイコン」なんて珍しくも無いと思うかも知れませんが、数百バイトの RAM と、数キロバイトの Flash(ROM) で構成された 8 ビットマイコンで実用に耐えるレベルで使う事が出来る C++(gcc) は、非常に珍しいと思いますし、フリーで制限無く使えます。
有志の方たちの努力で、非常に効率が良く、洗練された、品質の高いコードが出ます、又、数十種類にも及ぶ AVR ファミリーに特化した専用のヘッダーが用意されており、個々のデバイスに最適化した実装をシンプルに行う事が出来ます。

AVR マイクロコントローラーは、RAM や Flash(ROM) の容量や周辺機能の違いにより 100 円から数百円で、秋葉原などでも普通に買え、入手性も良く、又、プログラムを書き込むツールも安く売られています、必要な情報も公開されている為、自分で書き込み装置を製作する事もでき、これらのソリューションは、ホビーでハードウェアーを自作する人たちに好まれてきました。
IMG_0485s
2種類の自作 ISP (AVR にプログラムを書き込むツール)
日本でも色々な方が ISP を公開していますが、代表的なのは、「千秋ゼミ」 で、AVR への安価な書き込み機を紹介しています、このプロジェクトでは、100 円で買える最も安い AVR(ATtiny2313) を利用した ISP「HIDaspx」で、プログラミングループのマシンサイクルを厳密に調整して、USB1.1 の 1.5Mbps の接続をエミュレーションしています、そしてそれらを2キロバイトのメモリーに入れています、とてもクールなもので、素晴らしいソリューションです。
※「鶏と卵問題」がある為、HIDaspx が書き込まれた AVR が欲しい人は、連絡下さい、書き込み済みの物を送ります。(100 円+送料)

また、Arduino(アルドゥイーノ)と呼ばれる、統合開発環境があり、この環境では、C++ に似た(殆ど C++)Arduino 言語を用いて、プログラムを作成する事ができ、世界中の人が使っています、今回のシステムでは、Makefile で直接 avr-gcc を動かしますので、Arduino は使いません。

最近は、32 ビットの ARM マイコンが 110 円で入手出来る時代なので、その価値は多少薄れているようにも思いますが、電源電圧範囲が広く、低消費電力で、それなりに高速(最大 20MHz)、外部ペリフェラルも豊富で、関係資料が多く、とても使いやすいマイコンなので、ホビーで電子工作をする人のより良いアイテムとなっています。
※双璧を成す同じような構成のマイコンとして PIC がありますが、こちらは gcc は使えませんし、C++ も使えないようです、場合によっては有料の SDK を購入する必要があります。

今までは、AVR マイコンの開発では、主に C でプログラミングしてきましたが、C++ でどのくらい実用性があるのか、検証するつもりで、全て C++ による実装を行い、関連する問題を検証してみました。

最も大きな問題として AVR C++ の仕様では stdlibc++(STL が使えない) が無い点です、それでは C++ と言えないのではと言う人もいるかと思いますが、たとえば ATtiny2313 では 256 バイトの RAM と、2 キロバイトの Flash(ROM) しかありません、このようなリソースでは、STL ライブラリーを含める事は不可能です、多分、WinAVR の開発者は、リソースの制限から、STL を捨て、C++ によって得られる成果を最大限受け取れるように最適化したものと思われます。
もちろん、 boost もそのままでは使う事は出来ませんが、一部は、WinAVR 用に修正すれば、恩恵を受け取る事は可能だと思います。
良く、「車輪の再発明」と言うのがありますが、AVR に限っては、もし必要なら、小規模で独自な vector、string、map、のような何かは必要になると思います、都合が良い事に、参考に出来る資料や実装は沢山あるので、移植は難しい事では無いと思います。
ただ、メモリー消費と機能を天秤にかけた、中世期の実装、格闘となると思います。

今回 ATmega328P(RAM: 2K, ROM: 32K, Clock: 20MHz)のマイコン(250 円)を使い、16×16 のドットのマトリックス LED 表示を持った小型デバイスを作ってみました、表示だけではエンターティメント性に欠けるので、単音の矩形波で実現するパルスサウンドモジュレーター(チープですが)を組み込みました、そして、入力デバイスとして4つの押しボタンスイッチ、時計用のリアルタイムクロックも加えました。
IMG_0490s
※各モジュールを外して分解したところ
※この写真にはRTCなどが含まれていません。

ソースコード、回路図など GitHUB で公開していますので、ハンダ付けが出来るなら、回路製作をチャレンジしても良いと思います、なるべく汎用的な部品を使い低価格になるようにしています、今回 16×16 の LED モジュールは、秋葉原でたまたま見つけたジャンク LT-5013T (420 円)を使ったのですが、全体のコストは2000 円くらいでしょうか。
元々は、台所で使うキッチンタイマーをゴージャスに作るのが目的だったのですが、(ダイソーで買ったタイマーは味気がありませんし、最近誤動作するようになってきました)作ってみると、16x16 LED ドットマトリックスはチープなわりに意外と奥が深いのでミニゲームも作ってみました。

(1)ハードウェアーとソフトの分担
マイコンを使った「装置」を作る場合、ハードで分担する部分と、ソフトで分担する部分の比率や構成を考えなければなりません、ホビーで作る場合は、なるべくソフトの比重を多くして、簡単なハードで済ます方が何かと楽なものです、その中でとりあえず一番のトピックスは LED の表示方法です、この LED のマトリックスでは、16x16(256個)の LED がありますがマトリックス状に結線されている為、一度に16個を表示して、それを16回繰り返して全ての表示を行います(ダイナミックスキャンと言います)、フレームレートをゲーム機並みの 60 枚にしたかったので、LED のダイナミックスキャンは 960Hz で行っています、このくらい高速だと、人間の目にはちらつきは認識できません。
今回、LED のダイナミックスキャンでは、3 to 8 デコーダー(74HC138)を2個、8 ビットのラッチを2個、P チャネル FET 16個で行いましたが、後で気がついたのですが、 TLC5940 と言う IC があって、輝度調整も出来て、まさにこれが最適なようです。
※ダイナミックスキャンでは、LED が物理的に点灯している時間はこの場合1/16なので、目的の明るさにするには16倍の輝度(16倍の電流を流す)にする必要があります。

ゲームを作ると「音」が欲しいのですが、このマイコンには16ビットのタイマーが1チャネル内臓されており、タイマーの周期で信号をトグルさせる事で音を鳴らす事が出来ます(ビープ音と言っています)、これを利用して12平均音階率の周波数を数オクターブ出せれば、単音でも簡単な音楽を演奏する事もできます、多少誤差があるので、高音になる程オンチになります。
又、別の8ビットタイマーを使い、出力をパルス幅変調して、ローパスフィルターを通す事で、擬似的な電圧出力を出せます、これで、音の信号を変調すれば、音にエンベロープ(強弱)を追加する事が出来ます、また、12平均音階率の中心周波数に対して多少周波数を上下すれば、ビブラートのような効果を追加する事も出来ます、あまりやりすぎると、複雑になりすぎるので程ほどにしますが、チープなハードでも、ソフトを凝れば、相当色々な表現が出来る事は間違いありません、C++ でサウンドドライバー的な実装を行うと、非常に楽が出来、このような実装を書けるのはとてもありがたいです!

基本、キッチンタイマー(ゲーム)なので、入力装置も必要です、押しボタンスイッチを4個付けました、ところで、「チャタリング」と言うのは知っていますか?、物理的な構造で接点を ON/OFF して、電気信号をやりとりすると、ON した瞬間、OFF した瞬間は、数ミリ秒の期間(この時間は、スイッチの機械的な構造によって変化します)だけ、信号が暴れて ON/OFF を繰り返します、これを普通に読み出すと確率的に正しく読めないので、昔は、このノイズを取り除く方法として、フリップフロップ回路が考案され、実際に使われていました、現代では、ソフトウェアーによって、このノイズを取り除いています、実際には、画面の更新周期(フレームレート)が 60Hz なので、このタイミング(16.67ミリ秒)に合わせて、スイッチを読みにいくだけです、これで、チャタリングが取り除かれる理屈はサンプリング理論で説明できます、シンプルで賢い方法です、この方法で前提となっているのは、チャタリングが16ミリ秒より十分短いのが前提ですが、機械式接点のチャタリングは悪くても8ミリ秒程度です。

(2)C++ の準備
WinAVR の C++ では libstdc++ がありません、そこで、C++ 標準ライブラリーに含まれるいくつかのコードと、ワークアラウンドが必要です。
・お決まりの new、delete の実装

void * operator new(size_t size)
{
    return malloc(size);
}
 
void operator delete(void* ptr)
{
    free(ptr);
}

※AVR はメモリーが少ないので、もし必要なら、オブジェクトの生成や廃棄で、メモリーが断片化した場合の対策を行う必要があります。

・ガード関数などの宣言と実装

__extension__ typedef int __guard __attribute__((mode (__DI__)));
 
extern "C" int __cxa_guard_acquire(__guard *);
extern "C" void __cxa_guard_release (__guard *);
extern "C" void __cxa_guard_abort (__guard *);
 
int __cxa_guard_acquire(__guard *g) {return !*(char *)(g);};
void __cxa_guard_release (__guard *g) {*(char *)g = 1;};
void __cxa_guard_abort (__guard *) {};

・純粋仮想関数の宣言と実装

extern "C" void __cxa_pure_virtual(void);
 
void __cxa_pure_virtual(void) {};

これだけあれば、C++ として必要十分の事は出来るようになります。

(3)割り込み
組み込みで一般的なアプリケーションと違う部分は、割り込み処理でしょうか、OS を伴わないマイクロコントローラーのソフトウェアーでは、割り込みを直接扱います、また、割り込み関数と、メイン関数の共有リソース管理や同期処理などを正しく動かす為、コンパイラの最適化を制御しなければなりません。

//-----------------------------------------------------------------//
/*!
    @brief  ATMEGA168P タイマー2 割り込みタスク(タイマー0比較一致)
*/
//-----------------------------------------------------------------//
ISR(TIMER2_COMPA_vect)
{
    ledout_.reflesh();
}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  LED 出力クラス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
class ledout {

    static const uint16_t fb_width_  = 16;
    static const uint16_t fb_height_ = 16;

    uint8_t     fb_[fb_width_ * fb_height_ / 8];
    uint8_t     count_;
    volatile uint16_t   frame_;

......

//-----------------------------------------------------------------//
/*!
    @brief  ダイナミック・スキャンのリフレッシュ
*/
//-----------------------------------------------------------------//
void reflesh() {

.....

    ++count_;
    if(count_ >= 16) {
        count_ = 0;
        ++frame_;
    }
}

//-----------------------------------------------------------------//
/*!
    @brief  フレームの同期
*/
//-----------------------------------------------------------------//
void sync() const {
    volatile uint16_t cnt;
    cnt = frame_;
    while(frame_ == cnt) ;
}

↑の「ledout」クラスでは、frame_は、割り込み関数と、メイン関数で共有されます、sync 関数では、割り込み時に frame_ のカウントアップが行われる為、それを検出していますが、最適化を制御する為、「volatile」を使います。
※ ISR マクロは、WinAVR が定義しているデバイス毎の割り込み関数用マクロです。

(4)同期式プログラムとモーションオブジェクト
ゲームでは、ファミコン時代から、画面の更新(60Hz)に同期してプログラムを動かす方式が主流です、一般的なアプリが、メッセージを送受信して、それに応答するように設計されるのに比べて小規模なシステムでは、合理的な事が多く、細かい制御が楽な為です。
ゲームアプリケーションでは、絶対的な時間軸に沿ってプログラムを動かします、たとえば「移動」を考えた場合、停止している場合は、移動ルーチンをスキップしているのでは無く、速度を「0」にしているに過ぎません、移動ルーチンは、動いていても動かなくても常に通っています、全体的に、このような方法論により組み立てます。
しかしながら問題もあって、処理負荷が大きくなり過ぎると、画面の更新( 60Hz だと 16 ミリ秒)に間に合わなくなり、次の画面更新に持ち越してしまう為、見た目の速度が半分、又はそれ以下になってしまいます、昔のシューティングゲームなどで敵や弾が多く表示された時に遅くなる現象(ナムコでは「ぞうさん」と呼んでいましたww)です。
ゲームの開発者は、そうならないように、処理負荷を調整して、分散させるなど工夫をしていました。

ファミコンでは家庭用ゲーム機としては、多分初めてハードウェアーによる画像の管理機能が採用されました、これを一般的には「スプライト」と呼びます。
あらかじめ設定した二次元の絵をハードウェアーによる管理で自由に移動させる事が出来るものです、「モーションオブジェクト」と言うのは「アタリ社」が呼んでいたものです。
※初期のパソコンでは、グラフィックスのビットマップをソフトウェアー処理して、スプライトのような処理をしていました、その為、リアルタイムに同時に動かせるオブジェクトの数と大きさに著しい制限がありました。
※インベーダーが流行った時代、アーケードの違法コピーが沢山行われていました、これは、当時のアーケードゲームは、誰でも入手できる部品を使っていた為ですが、現在大手のメーカーもコピーを行っていたのは、痛い事です・・・
ところで、インベーダーの基板は、白黒のビットマップグラフィックスしか無く、モーションオブジェクトのハードウェアーは搭載されていませんでしたが、どこかのコピー屋が、ナムコのギャラクシアンをコピーしていました、当然、モーションオブジェクト回路は無いので、その部分はソフトウェアーエミュレーションなのですが、ギャラクシアンの編隊が出ると、「ぞうさん」状態になっていましたwww
※そんだけ、エンジニアリングがあれば、オリジナルゲームが作れると思いますが・・・

今回のハードウェアーでも、アプリケーションは同期式で制御しており、ドットマトリックスは、16×16のモノクログラフィックスとして処理を行っています、ダブルバッファによるフレームバッファ方式を採用して、スプライト的な機能をソフトウェアーで実現しています。
フレームの最初でフレームバッファをドットマトリックスのスキャンバッファにコピー後、フレームバッファを消去して、表示の再構成を毎フレーム行っています。

   while(1) {
        graphics::monograph& mng = task_.at_monograph();
        ledout_.copy(mng.fb());    ///< フレームバッファを LED 表示バッファへコピー
        mng.clear(0);              ///< フレームバッファの消去

        task_.service();           ///< 表示の再構成を行う為のタスクサービス

        uint8_t sw = sync_system_timer_();   ///< フレームと同期後、スイッチの状態をサンプリング
        task_.at_switch().set_level(sw);     ///< スイッチの状態を更新して、押した瞬間、離した瞬間を作成
    }

(5)サウンド
今回採用した方法は、単音でチープですが、基本的な構造は網羅してあります。
まず、音階ですが、一般的な音楽理論では、12平均率を使います、1オクターブを均等に12分割するので平均率と呼ばれます。
1オクターブで周波数は2倍となるので、12乗したら2になる定数 (1.059463) を求め、乗算の定数として使います、12回掛けると2倍になります。

    2 ^ (1/12) = 1.059463
    C    :  65.41 Hz
    C#/Db:  69.30 Hz
    D    :  73.42 Hz
    D#/Eb:  77.78 Hz
    E    :  82.41 Hz
    F    :  87.31 Hz
    F#/Gb:  92.50 Hz
    G    :  98.00 Hz
    G#/Ab: 103.83 Hz
    A    : 110.00 Hz
    A#/Bb: 116.54 Hz
    B    : 123.47 Hz

※ピアノの調律などでは、この周波数を微妙にずらしたりするようです、和音の響きとか、色々な流儀があるようです。
「ドレミファソラシ」は「ミ、ファ」が連なっていて、他が一つおきなのに、何だか妙な感じです・・・
オクターブと音階により、分周比を決定し、タイマーのレジスターに書く事で、希望の周波数が出力されます、出力はデジタルなので矩形波です。

これだけだと、非常に限られた表現しかできません、たとえば、ある楽譜で「ド、ド、レ」となった場合、「ド、ド」は、一つの長い「ド」として聞こえてしまいます。
そこで、強弱を加える事で、切れ目を作ります、エンベロープ波形などと呼ばれています。
しかし、AVR はデジタル出力しかありません、そこで、パルス幅変調(PWM)と言う方法を使います、PWM では、一定の周波数を短形波出力して、1と0の比率を変える事で、平均すれば、強弱を変化させている事に等しくなります、PWM の周波数は、人間の可聴範囲外にある(今回は 78KHz を設定)ので、PWM で変調する事により、全体として、音の強弱として聞こえます、最終出力には、RC フィルターを入れて、高い音の成分をなだらかにして、多少聞きやすくしています。

サウンドクラスでは、音階と音長の管理、他表現のコマンド管理などを行い、音楽を演奏する環境を整えています。
又、音楽を鳴らしながら、効果音を擬似的に鳴らせるように、音楽を演奏するトラックは2つ用意して、別々に管理して、優先順位を決めて、片方のトラックの音を鳴らしています、常時1音しか出ないので、音楽が鳴っている時に効果音が鳴ると、音楽が完全に途切れるのですが、仕方ありません・・
※あるいは、ソフトウェアーを工夫して、短い時間で時分割処理にするとかすれば、擬似的に同時二音も出来そうな気がしますが、複雑になり過ぎるので、妥協しました。

・キッチンタイマー用楽曲
   // ストラップ(doriko)、イントロ部分
    static const uint8_t bgm_01_[] PROGMEM = {
        device::psound::sound_key::TEMPO,   210,

        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          16,
        device::psound::sound_key::D + 12 * 5,  8,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::G + 12 * 5, 16,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::D + 12 * 5, 24,
        device::psound::sound_key::Q,          32,

        device::psound::sound_key::A + 12 * 5, 32,
        device::psound::sound_key::A + 12 * 5, 16,
        device::psound::sound_key::D + 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          16,
        device::psound::sound_key::D + 12 * 5, 16,
        device::psound::sound_key::A + 12 * 4, 16,
        device::psound::sound_key::Cs+ 12 * 5, 16,
        device::psound::sound_key::D + 12 * 5,  8,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          32,

        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          16,
        device::psound::sound_key::D + 12 * 5,  8,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::G + 12 * 5, 16,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::D + 12 * 5, 24,
        device::psound::sound_key::Q,          32,

        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::D + 12 * 5, 32,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          16,
        device::psound::sound_key::D + 12 * 5,  8,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::G + 12 * 5, 16,
        device::psound::sound_key::Fs+ 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::D + 12 * 5, 24,
        device::psound::sound_key::Q,          32,

        device::psound::sound_key::A + 12 * 5, 32,
        device::psound::sound_key::A + 12 * 5, 16,
        device::psound::sound_key::D + 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5, 32,
        device::psound::sound_key::Q,          16,
        device::psound::sound_key::D + 12 * 5, 16,
        device::psound::sound_key::Cs+ 12 * 5, 16,
        device::psound::sound_key::D + 12 * 5, 16,
        device::psound::sound_key::E + 12 * 5,  8,
        device::psound::sound_key::D + 12 * 5, 24,
        device::psound::sound_key::Q,          16,

        device::psound::sound_key::Q,          255,
        device::psound::sound_key::END
    };
・効果音
    /// 移動効果音
    static const uint8_t snd_move_[] PROGMEM = {
        device::psound::sound_key::A + 12 * 4, 1,
        device::psound::sound_key::C + 12 * 4, 1,
        device::psound::sound_key::E + 12 * 4, 1,
        device::psound::sound_key::END
    };

IMG_0514ss
スピーカー、パワーアンプ、ボリューム

(6)RTC(リアルタイムクロック)
作っているうちに欲が出てきて、RTCを載せる事にしました、時間が判ると便利なものです。
RTCは電源が切れている間も時間を刻む必要があるので、専用ICを使いました、今回は、DS1371を使います、この IC は、1秒間隔でカウントアップする、32ビットカウンターを内臓しています、電源が切れても、リチウムボタン電池によりバックアップを行っているので、時間カウントは休み無く進み続けます。
単純な秒のカウントなので、ソフトウェアーで、年月日曜日などを生成します、この部分は通常 libc の「time.h」関係に含まれるのですが、AVR では、それも無い為、独自に実装しています、この部分は、昔に作った C のソースを使っています。

コラム:「不思議な RTC の常識」
RTC は、かなり昔からある IC で、色々なメーカーが何百種類と色々な物を出しています、ただ、その構成や機能には疑問が残ります。
一般に非常に多く流通している構成は、DS1302 のようなもので、カウンターは、秒、分、時間、曜日、日、月、年のように分かれており、一見便利そうですがー、ソフトを作る立場では、大きなお世話で、余計な事をしている典型で、非常に扱いにくいものです、このような構成では、これらレジスターを集めて、一旦シリアライズして、また元に戻さないと、何もできません、まっとうな時計管理では、うるう年や、うるう年、曜日を求める事が必須なのですから。
今回使った DS1371 は扱い易くてシンプルな32ビットカウンターなのですが、購入するとなると、こちらの方が値段が高く、入手性も悪いのが現実です、今回は以前にサンプルで入手した物が手元にあったので使いました。
これは、初期の8ビットマイコン時代の悪しき慣習が見直されないまま、現在に至っている事だと思いますが、何とも、歯がゆいものです・・・
※時間を設定する時、曜日を入力させた。
※ちなみに、DS1371 は 150 円、DS1302 は 50 円です。

IMG_0513ss
DS1371

IMG_0516ss
バックアップ電池

(7)アプリケーションの実際
今回は、とりあえず、以下の機能を入れてあり、全体で19キロバイト程の容量となりました。
・キッチンタイマー
・3分、4分、5分、用タイマー(カップ麺用)
・ゲーム
・時計

(8)電源
AVR は5Vで動作させているので、リチウムポリマー電池(3.7V)から5Vを生成して、動かしています。
リチウムポリマー電池の充電はUSBから行えるようにしてあります。
※この部分は、昔にジャンクで買った、USBバッテリーをそのまま分解して使いましたwww
IMG_0512ss
※この部分は、大きさに難がありまだ載せていません・・・

(9)まとめ
今回、色々な部分をC++で実装してみて、STLが使えないながら、効率よく判りやすく実装する事が出来ました、そして、小規模環境でも、十分実用的にプログラミングが行える事が判りました、メモリーが少ない事があらかじめ判っていれば、そのような対応をすれば問題無く、少ないメモリーは工夫をする為の味付けと感じました。
ただ、以上の前提は、今まで、C++を習得してきた下地がある事で応用が利いているものと思います。
現状で、これだけ環境が整っているのに、C++で実装している AVR のホビースト(日本国内の感覚)が少ない現実を考えると、やはり、C++の習得には、一定のハードルがある物と思われますし、最低限実用になるまで習得するにはそれなりの時間と忍耐も必要なのだと実感します、自分も未だ習得は半ばですが、この楽しい言語の習得は続けていきたいと考えています。

今回の成果を github に上げてありますので、興味ある人は参照して下さい。

※ドットマトリックス用のビットマップデータ変換は、「ビットマップ変換ツール」を参照。

操作の動画

そして次回は hotwatermorning さんの記事です。

よろしくお願いします!

Just another WordPress site