「電子工作な日々」カテゴリーアーカイブ

電子工作に関連するお話など・・

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 さんの記事です。

よろしくお願いします!

RX63T を試用してみた

RX621 関係は、別件で忙しくて、中々進まない・・

前から、マイコン制御のインバーターやら、DC/DCコンバーターなどを実験してみたくて、色々、構想を練ったり、試作したり、壊したりしてたー

ブラシレスモーターの制御では、一般的には 20KHz 程度の PWM で、直接コイルをドライブする方式が主流で、ラジコンのモーターコントローラーとかがそれだ。
※大抵、8KHz と 16KHz を切り替え出来るようである。

また、正確なサイン波を作る 100V 用のインバーターを作りたいと思っていた。

それと、リチウムイオン電池の充電や、昇圧、降圧のような、アナログ回路が主流の分野をマイコンでどうにかする事を考えていた。
この辺のICとしてはリニアテクノロジーとかが多くのICをラインナップしているが、意外と割高で使うのをためらっていたのが現状。

DC/DC コンバーターはパワー MOS-FET の高速化と、コアの材料改良によって、かなり高いスイッチング周波数でも効率が落ちなくなってきている。

マイコンで、DC/DC のような物を実現する場合に問題となるのは PWM の周波数である、たとえば、20MHz のクロックでは、8 ビットの分解能だと、78.125KHz にしかならない、78KHz 程度では、小型のコイルを使う事が出来ない為、不十分だ・・

そんなこんなで RX マイコンであるー
RX63T の PWM は 100MHz で駆動できる、8 ビットの分解能なら 390KHz、9 ビットでも 195KHz で駆動できる、しかも、コアも 100MHz で動作する為、かなり高速な応答に備える事が出来そうである。
これを前から実験してみたくて、ウズウズしてたら、R5F563T6EDFMを小売してる事が判り、早速購入してみた 582円、構成は、64KFlash、8KRAM、と丁度手頃な感じで、RX621 のリソースを流用出来ると考えていた。

最近、ようやく、試作して、Lチカまで出来たので紹介する。

IMG_0520ss

それで判った事は、RX63T(63系) は 62系 と構成が変わり、62 系のプログラムでは動作しない事だ。
大きな違いは以下
・クロック回路が大幅に拡張されている。
内部発振で125KHzのクロックが使える。
PLL 回路の構成が拡張され、外部発振のクリスタルや、入力クロックの柔軟性が上がった。
各モジュールの分周回路が拡張された。
柔軟性が上がったのは良いが、クロックの設定が面倒になった。

static volatile uint8_t dummy_;

static void wait_()
{
    // とりあえず無駄ループ
    for(uint32_t i = 0; i < 5000; ++i) {
        i += dummy_ & 0;
    }
}

int main(int argc, char** argv)
{
    using namespace root;

    device::SYSTEM::PRCR = 0xa503;  // クロック、低消費電力、関係書き込み許可

    device::SYSTEM::MOSCWTCR.MSTS = 0b01101;    // 131072 サイクル待ち
    device::SYSTEM::MOSCCR.MOSTP = 0;           // メインクロック発振器動作
    dummy_ = device::SYSTEM::MOSCCR.MOSTP();
    device::SYSTEM::MOFCR.MOFXIN = 1;           // メインクロック強制発振
    dummy_ = device::SYSTEM::MOFCR.MOFXIN();
    wait_();

    device::SYSTEM::PLLCR.STC = 0xf;            // PLL 16 倍(192MHz)
    device::SYSTEM::PLLWTCR.PSTS = 0b01010;     // 131072 サイクル待ち
    device::SYSTEM::PLLCR2.PLLEN = 0;           // PLL 動作
    wait_();
    device::SYSTEM::SCKCR = device::SYSTEM::SCKCR.FCK.b(3)      // 1/8
                          | device::SYSTEM::SCKCR.ICK.b(1)      // 1/2
                          | device::SYSTEM::SCKCR.BCK.b(2)      // 1/4
                          | device::SYSTEM::SCKCR.PCKA.b(1)     // 1/2
                          | device::SYSTEM::SCKCR.PCKB.b(1)     // 1/4
                          | device::SYSTEM::SCKCR.PCKC.b(3)     // 1/8
                          | device::SYSTEM::SCKCR.PCKD.b(3);    // 1/8
    device::SYSTEM::SCKCR3.CKSEL = 4;   ///< PLL 回路選択

...

}

※以前は、USB を使いたい場合、12MHz の基準周波数を外せなかったが、今度はもう少し柔軟性がある。

・プロテクト回路が新設された。
これは、自動車の ECU や大型機械の制御など、より信頼性が必要な分野にコミット出来るようにしたものだと思われるが、重要なレジスター関係(クロック設定や消費電力削減関係など)には、プロテクト回路をセーフ状態にしないとアクセス出来ないようになった。
※これが判らなくて、はまった・・・www

・シリアルインターフェースが拡張された。
本来シリアルインターフェースの考え方(送信や受信の手続き)は、I2C や、SPI 通信と共有できる部分が多い、今回「簡易」とついているが、シリアルインターフェースが拡張されて、これらの機能が使えるようになった。

※他にもあるけど、とりあえずこんな感じ。
※現状のソースコードを github にプッシュしてある。

さて、次はいよいよ、とりあえず、降圧の DC/DC コンバーターを作ってみる。

※追記:2013年12月2日(基本回路)

回路図まで手が回らないので、とりあえず、ピン接続を書いておきます。
※ピン番号は 64 ピン版です(R5F563T6EDFM) 64K Flash、8K RAM

基本的な接続:
EMLE(1)  ---> GND (オンチップエミュレーター許可端子、許可する場合 VCC へ)
P01(4)   ---> 10K でプルダウン(ユーザーブートとブートの切り替え用)
VCL(3)   ---> 0.1uF のコンデンサを介して GND へ
RES#(6)  ---> プルアップ又は 3.3V 系リセットICへ
XTAL(7)  ---> 12MHz クリスタル(22pF のコンデンサで GND)
EXTAL(9) ---> 12MHz クリスタル(22pF のコンデンサで GND)
※PLL 回路や、周辺回路がリッチなので、色々な周波数を選択出来ます。

電源:
VSS(8)  ---> GND
VSS(22) ---> GND
VSS(44) ---> GND
VCC(10) ---> 3.3V
VCC(20) ---> 3.3V
VCC(42) ---> 3.3V
※各電源端子には 0.1uF のバイパスコンデンサを入れる。(3個)

セルフブート(Flash への SCI からの書き込み)に必要なピン:
MD(5) ---> Low: ブートモード、High: シングルチップモード

オプション設定メモリー:
FFFFFF80 ~ FFFFFF83: はシングルチップ時のエンディアンを選択します。
※通常何も設定しなければ、Flash のイレース値が 0xFF となる為リトルエンディアンとなります。
※その他の初期状態設定(OFS0、OFS1)は「オプション設定メモリー」の項を参照して下さい。

AKI-RX62で「C++」を使わない理由が無い!

RXマイコン用、C++デバイス制御クラスを実装していて、色々と少しづつアップデートしている。

今日の標語:

  「C++」を使わない理由が無い!

RX621に内臓されているハードウェアーリソースは豊富なので、まだ、全ては実装出来ていないけど、とりあえず、必要そうな定義は実装してみた。
※十分なテストとかしていないので、バグがあるかもしれないけど・・・
※一番ありがちなのは、アドレス定義ミス。

各ソースコードと機能の対応:
※ルネサスの「RX62Nグループ、RX621グループユーザーズマニュアル ハードウェア編」を元にしている。

rx62x_system.hpp    システム制御関係の定義(リセット、電圧検出回路、クロック発生、消費電力削減)

rx62x_icu.hpp       ICU(割り込みコントローラー)の定義

rx62x_port.hpp      I/Oポートの定義

rx62x_dmaca.hpp     DMACA(DMAコントローラー)の定義
rx62x_exdmac.hpp    EXDMAC(EXDMAコントローラー)の定義
rx62x_dtc.hpp       DTC(データトランスファーコントローラー)の定義
rx62x_crc.hpp       CRC の定義

rx62x_mtu.hpp       マルチファンクションタイマユニットの定義

rx62x_i2c.hpp       I2C(RIIC) バスインターフェースの定義
rx62x_i2c_io.hpp    I2C の制御
rx62x_i2c_io.cpp

rx62x_rspi.hpp      RSPI の定義
rx62x_rspi_io.hpp   RSPI の制御
rx62x_rspi_io.cpp

rx62x_cmt.hpp       CMT(コンペアマッチタイマー)の定義
rx62x_cmt_io.hpp    CMT の制御
rx62x_cmt_io.cpp

rx62x_sci.hpp       SCI(シリアルインターフェース)の定義
rx62x_sci_io.hpp    SCI の制御
rx62x_sci_io.cpp

rx62x_rtc.hpp       RTC の定義
rx62x_rtc_io.hpp    RTC の制御

rx62x_s12ad.hpp     S12AD(12ビットA/Dコンバーター)の定義
rx62x_ad.hpp        AD(10ビットA/Dコンバーター)の定義
rx62x_da.hpp        DA(10ビットD/Aコンバーター)の定義

全ソースコード

☆テンプレート化してあるので、複数チャネルがあるデバイスは全て同じように扱える。
☆デバイスのテンプレートは、多少工夫してあるので、判りやすく、間違えば大抵エラーとなる。

DMACA、4チャネル
EXDMACA、2チャンネル
I2C、2チャンネル
RSPI、2チャンネル
CMT、4チャネル
SCI、6チャネル、※何故か4チャンネルは無く(0、1、2、3、5、6)
※SCIは、シリアル入力(RxD)に対応するポートを入力に指定する必要がある。

ルネサスがサンプルなどで提供しているデバイスの定義ファイルを捨てて、とりあえず、現状、定義してあるデバイスヘッダーでコンパイル出来るように変更した。
この修正で、必要な部分は、全て C++ で実装されているので、それを使う事が出来る。

fatfs は、RXマイコン依存の部分を新しいデバイステンプレートを使って、C++でコンパイル出来るように修正した。
※基本的に、えるむさんのコードはそのままになっている。
※サンプルにあったRXマイコン用のコードは、C++デバイス定義用「mmc_rspi.hpp、mmc_rspi.cpp」に修正されている。

プログラムの動作について:
・RxD0、TxD0 にターミナルを接続すると、簡単なシェルが起動する。(115200、8ビット、1ストップビット)
・RTC は外部の I2C に DS1371 を接続する。(ds1371_io.cpp でどのポートを使っているか判る)
※RX621 内臓の RTC は、バッテリーバックアップ出来ないので仕方なく接続・・・
※RX621 内臓の RTC を使う事も出来る、コードは一応用意した。(rx62x_rtc_io.hpp)
※「sd_monitor.cpp」で、どちらを使うか選択出来る。
・VS1063a の接続する場合、ソースコードを確認の事。
・「syscalls.c」で、POSIX のファイル操作(SDメモリーに対して行う)を実装してあり、fopen 系、printf など普通に使う事が出来る。
・「init.c」で静的に宣言しているクラスなどのコンストラクターが呼ばれる。
・「stdc++」ライブラリーをリンクしているので、STL も使える。
・コマンドは、以下

% ls         SD カードのディレクトリーを表示
% cd xxx     カレントディレクトリーを移動
% date       日付、時間を表示、設定も可能
% play xxx   オーディオファイルを再生(現在はポーリングとなっている)
% volume     ボリュームの呼び出しと設定

このフレームワーク?では、C++ で普通にプログラム可能なのだが、iostream は使わない事、メモリを非常に食うので、フラッシュに入らないと思う。
※ C++ だけど、とりあえず、printf を使って欲しい。
※当然ながら、iostream と関連性が強く、インクルードしてあるソースも避ける事。
STL は普通に使えるし、boost も全てでは無いけど使える~(iostream に関連する制限がある点に注意)

リンクに、変な警告が出ているけど、とりあえず無視するしかないが、問題は無さそう~
※このエラーが何故出るのか不明・・・、出ないようにする方法が不明なので、そのままになってる。

------
I2C や RSPI のデータ転送を DMA ベースにしたいとか、色々やる事があるけど、その前にお約束の LCD を繋ぎたい~

※バグを発見したり、質問があれば書きこんで~

C++ でI/Oデバイスの操作を考える

組み込みでもC++と言う事で、最近では、RXマイコンでC++0Xを使ってソフト開発をしていますが・・

ルネサスなどのサンプルでは、I/Oデバイスの操作は、C言語で使えるビットフィールドを多用しています。
サンプルに付属のハードウェアーデバイスを定義してあるヘッダー「iodefine_renesas.h」がその典型例です、しかしながら、規約では、8ビットや16ビットの定義は処理系依存であったと思います。
又、エンディアンが変わった場合も少なくと gcc では問題あります。
それなのに、このやり方が使われているのは大きな問題だと感じています。

ルネサスが試用版としてダウンロード出来るコンパイラでは問題無いようですが、gcc では問題となります。
※やった事は無いのですが、ルネサスのCコンパイラで boost が正しくコンパイルできるか不明ですし、C++11を使いたいし、IDEは嫌いだしで、積極的に使いたいとは思えません。
※時間が出来たら、RX用のLLVMをポートしたいです。
特に gcc では、最適化が施された場合に、32ビットでしかアクセス出来ないI/O空間に、バイトでアクセスするようなコードが出てくる場合もあります。
※これはRX用 gcc の問題なのかもしれません。

又、C++の強みを生かしたコーディングをしたいのもあります、今回RXマイコン用のコードを作る過程で、C++でI/Oデバイスを操作するライブラリーを実験、考えてみました。
自分のC++スキル以内の事しか出来ませんが、とりあえず、我慢できる程度の物が出来たので紹介します。

まず、どんな感じで書きたいか考えます。
RXマイコンに備わっているI/Oポートを例に考えてみます。

    device::PORT2::DDR.B3 = 1; // PORT23 output (/xCS)
    device::PORT2::DDR.B2 = 1; // PORT22 output (/xDCS/BSYNC)
    device::PORT4::DDR.B7 = 0; // PORT47 input (DREQ)
    device::PORT4::ICR.B7 = 1;
    device::PORT2::DR.B3 = 1; // xCS=H
    device::PORT2::DR.B2 = 1; // xDCS=H

とりあえず、こんな感じでしょうか?
※名前空間は「device」としています。

template <uint32_t base>
struct portx_t {
    typedef io8<base + 0x00> ddr_io;
    struct ddr_t : public ddr_io {
        using ddr_io::operator =;
        using ddr_io::operator ();
        using ddr_io::operator |=;
        using ddr_io::operator &=;

        bits_t<ddr_io, 0, 1>  B0;
        bits_t<ddr_io, 1, 1>  B1;
        bits_t<ddr_io, 2, 1>  B2;
        bits_t<ddr_io, 3, 1>  B3;
        bits_t<ddr_io, 4, 1>  B4;
        bits_t<ddr_io, 5, 1>  B5;
        bits_t<ddr_io, 6, 1>  B6;
        bits_t<ddr_io, 7, 1>  B7;
    };
    static ddr_t DDR;
};
typedef portx_t<0x0008c000> PORT0;
typedef portx_t<0x0008c001> PORT1;

--------
オペレーターの定義として、「=」、「()」、「&=」、「|=」を定義してあります。

「=」オペレーターの戻り値をvoidとして値を返さないようにしてあります。
PORT0::DDR = 0xf0;
と代入は出来ますが・・・
uint8_t data = PORT0::DDR;
とは出来ません、代わりに、
uint8_t data = PORT0::DDR();
とする事で値を取得できます。
これには、幾つかの側面があって、I/Oに対する「読み出し」と「書き込み」は同じアドレスであっても全く別の操作だからです。
又、最適化によって起こる問題を回避できると考えています。
※又、「=」オペレーターを無効にする事により、リードオンリーのデバイスを扱えます。(書き込もうとするとコンパイルエラーになります)

さて、バイト操作が出来たので、ビット操作を考えてみます。
※ bits_t の定義がそれに辺り、アクセス開始ビットと、アクセスするビット幅を定義してあります。

template <class T, uint8_t pos, uint8_t len>
struct bits_t {
    static typename T::value_type get() {
        return (T::read() >> pos) & ((1 << len) - 1);
    }
    static void set(typename T::value_type v) {
        typename T::value_type m = ((1 << static_cast(len)) - 1) << pos;
        T::write((T::read() & ~m) | (v << pos));
    }

    void operator = (typename T::value_type v) { set(v); }
    typename T::value_type operator () () { return get(); }
};

テンプレートに渡すT型のクラスは、8ビット、16ビット、32ビットのアクセス幅により決定します。

    typedef io8<base + 0x00> ddr_io;
    bits_t<ddr_io, 0, 1> B0;

※上の例では、io8 クラスを使っています。

io8 はこんな感じです。

template <uint32_t adr>
struct io8 {
    typedef uint8_t value_type;
    static void write(uint8_t data) { wr8_(adr, data); }
    static uint8_t read() { return rd8_(adr); }void operator = (value_type data) const { write(data); }

    value_type operator () () const { return read(); }
    void operator |= (value_type data) const { write(read() | data); }
    void operator &= (value_type data) const { write(read() & data); }
};

実際にハードウェアー上のアドレスにアクセスするのは、以下です。

static inline void wr8_(uint32_t adr, uint8_t data) {
    *reinterpret_cast<volatile uint8_t*>(adr) = data;
}
static inline uint8_t rd8_(uint32_t adr) {
    return *reinterpret_cast<volatile uint8_t*>(adr);
}

まだ冗長な部分がありますが、とりあえずやりたい事は出来ています。

※完全なソースコードは、AKI-RX62 でMP3再生(VS1063a)を参照して下さい。

※参考文献:
C++テンプレートテクニック

追記、修正: 2013,7,21
operator は継承出来ないので、冗長となっていた部分、using 文で、シンプルに書ける事が判った為修正。
※using ってこんな感じで使えるのか・・
※RX のソースアーカイブは、別の作業(RSPIの割り込み化)をしているので、それが終わってから更新します。

追記、修正: 2013,7,25
コンパイルした場合にどのようなアセンブリコードが出てくるか検証しました。

int main(int argc, char** argv)
{
    // ICLK=96MHz, BCLK=48MHz, PCLK=48MHz SDCLK出力,BCLK出力
    device::SYSTEM::SCKCR = 0x00010100;
    device::SYSTEM::MSTPCRA.MSTPA15 = 0;  // B15 (CMT)のストップ状態解除
    device::SYSTEM::MSTPCRB.MSTPB31 = 0;  // B31 (SCI0)のストップ状態解除
    device::PORT3::DDR.B0 = 1;            // PORT3:B0 output
    device::PORT2::ICR.B1 = 1;            // PORT2:B1 input (RXD0)
}

-O2 で最適化した場合のアセンブリコードです。

fff80a64 <_main>:
fff80a64: fb ee 20 00 08         mov.l    #0x80020, r14
fff80a69: f8 ee 00 01 01         mov.l    #0x10100, [r14]
fff80a6e: fb 4e 10 00 08         mov.l    #0x80010, r4
fff80a73: ec 4e                  mov.l    [r4], r14
fff80a75: fb 3e 14 00 08         mov.l    #0x80014, r3
fff80a7a: 77 2e ff 7f ff         and      #0xffff7fff, r14
fff80a7f: e3 4e                  mov.l    r14, [r4]
fff80a81: ec 3e                  mov.l    [r3], r14
fff80a83: fb 4e 03 c0 08         mov.l    #0x8c003, r4
fff80a88: 74 2e ff ff ff 7f      and      #0x7fffffff, r14
fff80a8e: e3 3e                  mov.l    r14, [r3]
fff80a90: cc 43                  mov.b    [r4], r3
fff80a92: fb ee 62 c0 08         mov.l    #0x8c062, r14
fff80a97: 65 13                  or       #1, r3
fff80a99: c3 43                  mov.b    r3, [r4]
fff80a9b: cc e4                  mov.b    [r14], r4
fff80a9d: 66 01                  mov.l    #0, r1
fff80a9f: 65 24                  or       #2, r4
fff80aa1: c3 e4                  mov.b    r4, [r14]
fff80aa3: 02                     rts

及第点でしょうか・・

AKI-RX62 でMP3再生(VS1063a)

I2Cの割り込み化をしなければならないが・・、とりあえず、置いといてー

SDカードが読めるようになったので、MP3の再生を行いたい~

オーディオのコーディックICと言えば、VSxxxが思い浮かぶ、専用DSPと、デコーダー、エンコーダーのプログラムを内臓して、低消費電力で、非常に多くのフォーマットに対応している。

最近、秋月で、VS1063aが発売になった、結構最新のデバイスで、新規に新しいフォーマットに対応して、内部も、細かい所が色々とアップデートされている。
800円とちょっと高いが、I2S出力も可能なので(VS1053も可能)、今回は、このデバイスを使ってみたい。
RXマイコン100MHzでは、ソフトウェアーで、MP3のデコードは可能なようだけれど、処理負荷は大きく、他に、何も出来なくなってしまうと思うので、専用ICを使った、他にグラフィックスもやりたいし・・

そろそろ、端子をどのように使うか、戦略を立てないと駄目なようだー
SPIは、SDカードで1チャンネル、VS1063で1チャンネル占有する感じで、他にSPIのデバイスがあった場合は、考えないといけない・・
※SDカードとVS1063はシェア出来るのかもしれないが、動いてから、別途考える事にするとして・・・

他の重要なデバイスとしてグラフィックスコントローラーがある、これは、メモリーマップにアサインする予定なので、D0~D15、Ax、/CS、/RD、/WRなどを開ける必要があり、それらのポートは使わないで開けてある。

ピンアサインはとりあえずこんな感じだ・・
RX側                VS1063a側
(P26)MOSIB-A  ---> SI
(P27)RSPCKB-A ---> SCLK
(P30)MISOB-A  <--- SO
(P23)         ---> /xCS
(P22)         ---- /xDCS・BSYNC
(P47)IRQ15-B  <--- DREQ

※他の接続は、参考回路を参照の事、VS1063aは、コアの電源として、1.8Vが必要なので、レギュレーターを別途使用する。
※クリスタルは、12~13MHzとなっていて、内部のPLLで、必要なクロックを得る事が出来るが、補正が必要無い、12.288MHzを使う事とした。

VS1063aは、0.5mmピッチの48ピン、LQFP-48フラットパッケージで、ちょっとだけハンダ付けが大変だ、フラックスと吸い取り線があれば、まぁ大抵大丈夫だが、変換基板にICを位置合わせするのが大変で、ここで失敗すると、取り返しが付かないので、慎重に作業する、レンズで拡大しながら、正確に位置を決め、二箇所程度にハンダを流して仮止めしてから、全部のピンをハンダ付けする、ショートしたら、吸い取り線で余分なハンダを吸い取る、出来たら全体を拡大して、ブリッジや、ハンダが足りない部分を慎重に見極めて、修正する。

IMG_0457ss

VS1063aは、ピン数は多いけど、電源端子が多い、試作とは言え、AGND、DGND、3種類の電源(I/O、コア、アナログ)とパスコンを慎重に接続する。
※チップ部品が、場所をとらず、2.54mmの間隔に丁度はまると楽チンだww。
※I2Sを実験する予定なので、それらの端子は、個々にプルダウン抵抗を設けておいた。

IMG_0458ss

端子を全部接続したら、早速テスト用ソフトのコーディング、SPIを使うので、前から懸案となっていた、デバイス関係を操作するC++ライブラリーの試作と実験を行った。
前にも書いたけど、C言語で、構造体にビットフィールドを宣言して、それによってデバイスをアクセスする方法は邪道、確か、言語規約的には、32ビット以外の挙動は保障されていなかったハズで、8や16ビットのアクセスによる動作は、処理系に依存するハズだ、それに、色々、不都合もある。
・エンディアンの違いを上手く吸収出来ない。
・シリアルのように複数チャネルあるデバイスでは、ドライバーを1チャネルだけ実装して、それを別チャネルでも等しく利用するのが難しい。
・リードオンリーのデバイスに書き込みを行えてしまう。
・最適化によっては、正しく無いコードが出来てしまう。(※これは rx-gcc のバグなのかもしれない)

「C++使ってるのだから、C++風に書きたい」と言うのが大きい。

で、色々考えて、C++の少ないスキルで、テンプレートを使って、作ってみたー、記述が冗長になっている部分はあるが、おおむね我慢できる範囲でまとまった。
※この話は、今度、別のブログで詳しく紹介したい、ソースコードがあるので、興味ある人は見てください。「rx62x_rspi.hpp」とかがそれです。

-----
さて、RSPI の操作をC++で出来るように記述して、とりあえずポーリングでVS1063aと通信する機能を実装して、VS1063a用ドライバーを作成した。
VSシリーズの解説では、放課後の電子工作さんが大変に詳しく、参考にさせてもらった。
通信が出来て、レジスターへの書き込み、読み出しが出来るようになったので、「テストモードの正弦波の発生」用ストリームを流してみたが、一向に正弦波を出力しない?
ハードを色々調べても、まずい部分は見つからず、どうしたもんかなぁーと悩むこと半日・・・、SDカードのテストもあるし、試しにMP3ファイルを流してみた、普通に鳴るではないか・・・

最終的にはI2S出力にD/Aコンバーターを付けて鳴らしたいので、あまり期待して無かったが、ヘッドホン出力も、それ程悪い訳でもなく、普通に聞ける~

まだ、ソフトが荒削りな部分が多く、参考程度に考えてもらいたいが、一応ソースコードを公開する。

ターミナルから、play xxx.mp3 とやれば再生する、多分、Ogg Vorbis、AAC も再生できるハズ。
それにしても、VS1063aは極めて簡単で、コンビニエンスなデバイスだ。

AKI-RX62 に、RTC(I2C)を接続する

最近は、RXマイコンのソフトウェアーを少しづつ充実させてきている。

「I2C」もその一つ。

I2Cのデバイスはかなり色々あるので、ドライバーを用意しておけば、今後便利に使えるだろう。
今まで、I2Cのデバイスを本格的に使った事が無かったので、今回実験してみて、色々な事が判った。

今回は、以前にサンプルで貰った、マキシム社扱いのDS1371と言うRTCを接続してみた、RX621にはRTCは内臓なのだが、バックアップが出来ないようなので使い物にはならない。
※電源を切ったら時計は止まってしまう、電源を入れた時に、また時刻を設定しなければならない、こんな仕様では、RTCとは言えないだろう。

仕方なく、外部にRTCを付ける事になる、それから言いたいのは、RTCの一般的な仕様について・・
一般的なRTCは、秒、分、時、日、月、年、うるう年など、色々なカウンターで構成されている、しかしながら、実用的な時間管理を行う場合、秒単位で時間を計算しなければならず、それには、一旦、秒でシリアライズされた値に変換して、計算を行い、time、date に戻す必要があり、カウンターが time、date では二度手間になるだけで、むしろ余計なお世話と言わざるをえない。
この time、date 仕様は、時間が判ればOKなようなチープな機器なら良いかもしれないけど、現代の機器、それも32ビットマイコンの組み込み機器では、ソフトでどうとでもなるので、単なるバイナリーカウンターの方が良い。

DS1371は、32ビットのバイナリーカウンターを持ったRTCで、数少ない単なるバイナリーカウンターのRTCだ、ただ残念なのは、値段はそんなに安く無い事だ、むしろ、time、date 仕様のRTCの方が安い。
※1000個単位ならそこそこ安いが、それでも、もっと安いRTCが沢山ある。
マイクロチップMCP79410
※DS1371は、クリスタルの外付けコンデンサを内臓しているとこが良い。

それでは、早速I2Cについて・・
I2Cを敬遠していたのは、面倒そうだから・・、今回、色々ソフトを作ってみて、思った通り、かなり面倒で、気を配る必要がある事が判った。
・処理が複雑になりがちで、難しい部分が非常に多い。
・プログラムをポーリング処理にすると、待ち時間でCPUを無駄に消費するので、最低でも割り込み処理にしないと実用的では無いだろう。
※DS1371からデータを4バイト取得するだけでも、100Kbpsだと、最低でも1ミリ秒はかかる・・
※割り込みでCPU時間を節約しても、「値」が欲しいと思ってリクエストしても、実際の値を得るまでにかかる時間は一緒なので、全体のマネージメントや戦略を考えないといけない。
・デバイスの仕様やバグで、I2Cのバスが思ったように応答せず、ハングアップする事があるので、そこから復帰させるルートを考えておかなくてはならない。

まだ他にも色々あるけど、I2Cは、優れた規格とは言えないと思う、が、一度、「糞」でも流通してしまうと、みんなそれに従うのが、痛たすぎる・・・

とりあえず、ポーリング仕様のI2Cドライバーを追加して、DS1371の読み出し、書き込み、時刻設定コマンドなど追加してみたので、ソースコードを更新した。

多少、バグなども修正した。

IMG_0450s

AKI-RX62 で SDカードを読む

そろそろ、RXマイコンも何とかしないとー、と思い、とりあえず、SDカードを接続してみた。

ネットで探すと、AKI-RX62にSDカードを繋ぐ記事は少ないようなので、同じような事を考えている人は参考になると思う。
※コンパイラは gcc を使っているが、コンパイラの依存をなるべく少なくなるようにしているので、ルネサスのIDEでもコンパイル出来ると思う。

まず、SDカードの前に、シリアルコミュニケーションを何とかしないと駄目だーー
それで、とりあえず、SCI0だけ使える、モジュールを作った。

シリアルは、以下のピンを使う。
P20(TxD0)
P21(RxD0)
※FTDIの変換モジュールを使ってUSB接続

rx62x_sci.h、rx62x_sci.c

「ポーリング」、又は、「割り込み」のどちらかで、初期化すれば、後は、ある程度適当にやってくれる。
※「ポーリング」はあまりちゃんとテストしていないので、あしからず・・・
※処理負荷も低いし、「割り込み」モードで問題無いハズだ、バッファの大きさを変えたい場合は、ソースコードで define されてるので、好きな大きさに変えられる。
※割り込みレベルも変更できるようにしてある。

自分が使っている gcc では、ビットフィールドを使ってデバイスを操作すると、まともに動作するコードが出来ない為、とりあえず直接ビットパターンを書き込むようなコードとなっている。
※そのうち、この問題は解決しなければならないのだが・・・
そもそも、ビットフィールドの操作は、コンパイラ依存の部分が多いし、エンディアンの違いなども関係する為、使わない方向で考えている。
最終的には、処理系や、エンディアンなどに依存しない実装を考えないと駄目な感じ。(これはそのうち・・)
※ルネサスのサンプルは、標準的にビットフィールドを使ってるみたいなので、何とかしないと・・・

SCIが動くようになったので、早速SDカード、まず、FatFsをダウンロードする。
※サンプルプロジェクトには、RX マイコン用の物もあるので、「mmc_rspi.c」を参考にAKI-RX62用に改修させてもらった。
※define マクロは、inline 関数に変更してある、define マクロは、罠にはまりやすく、可読性を悪くし、良い事は殆ど無い、速度も特別速くならない、それなのに未だに良く使われているのは不思議で滑稽だ。
※このソースでは、RSPI0を使って、パラレル/シリアル変換をハードで行っているので、かなり高速に通信できるようだ。

SDカードとの接続は、以下のピンで行っている。
PC7(MISOA-A)  SDO
PC6(MOSIA-A)  SDI
PC5(RSPCKA-A) CLK
PC4           /CS
PC3           /WP(ライトプロテクト)
PC2           /CD(カードディテクト)
GND           GND
VCC           3V3
※VCCの電源ラインは、カードが接続された場合に大きな電流が流れて、電源が不安定になるので、フェライトビーズを入れる。
※ソースコードにはコメントを沢山入れているので、自分のシステムに合うように容易に改修できると思う。
※パスコンは適当に入れておく事。

SDカードのソケットは、ヒロセSDカードコネクタが最高だと思ってるのだけど、ピンが狭い部分があり、ユニバーサル基板に取り付けるのは厳しいと思い、SDカードソケットモジュールを使う事にした。
前に「安かった」ので買っていたのだが、いざ、基板に付けてみると、「ライトプロテクト」が無いじゃん~・・・、まぁいいかー、試作だしー、と思ったのだが、SDカードを入れたり出したりしてみると、??
このCD(CardDetected)って、WriteProtectなのでは!?、
じゃぁ、CDは???、Google 先生に聞いて、このカードソケットの図面を探してみたー、あったー、まさしく、このソケットに違いない~、で、「CD」は?、うーーんGNDに接続されてるし・・・、しかた無いので、パターンをカットして、CDを取り出してみた。

IMG_0449ss
※この誤植は、秋月に一応伝えておいた。

ちなみに、マイクロSDは、小さくて良いのだが、小さすぎて無くしそうな感じなので、イマイチ好きになれない・・・www

それにしても、ELMさん(ChaN)のFatFsは、本当に良く出来ている、こんなに簡単に組み込めるのは、高品質なソースコードの所以であると思う。

文字コードをどうするか?
FatFs は、「ffconf.h」の設定で、デフォルトの文字コードを選ぶ事が出来ます。
そこで、利便性を色々考えて・・・

#define _LFN_UNICODE 1 /* 0:ANSI/OEM or 1:Unicode */

とします、こうすると、ファイル名の扱いが、全て WCHAR 型(unsigned short)になりますが、色々と一番扱いが楽だと思います。
しかし、シリアル出力などは、8ビットなので、UTF8などに変換が必要です、UTF8とUTF16は、論理的な相互変換が出来るので、比較的楽です。

さて、SDカードが読めて、シリアルコンソールが繋がると、もっと本格的にソフトの実装が出来ないと利便性がありません・・
そこで、syscalls の実装を行いました。
これは、以前にSH2Aで実装したリソースの再利用なのですが、一応実装されただけで、まだテストは出来ていません・・
syscalls の実装を行う事で、printf やファイルのオープン(fopen)など、POSIX の関数が普通に使えるようになります。
※SDカードは自動的にマウントするようになっています。(刺してから1秒くらい待ちます)

このプログラムを書き込んで、起動すると、「SD Monitor」なる物が起動します。
コマンドは、「ls」(ファイルのディレクトリーリスト)のみです。www
STL も普通に使えるみたいです、これから詳細な検証。

※リンク時に、気になる警告が出ますが、問題無いようです・・・

全体のソースコード

あと、リアルタイムクロックを乗せないと駄目な感じです、RXマイコンにもRTCは付いてますが、バッテリーバックアップが出来ないので、全く使い物になりません。

IMG_0448s

L6470 ステッピングモータードライバー

例によって秋月で、L6470ステッピングモータードライバーのモジュールを買った(750円)。
※ステッピングモーター駆動のタコメーターを作る予定で買った。

早速動かしてみたので、その報告。

L6470 は、最大 48V、定格 3A のドライバーで、128 マイクロステップが可能なインテリジェントドライバーだ。
制御は SPI で行う。
※このLSIは、位置と速度を管理していて、単なるドライバーとは言えない高機能なものだ。

「マイクロステップ」とは、ステッピングモーターの2つの相に流す電流の比を制御して、より細かいステップで制御する方法で、2相(4相)ステップモーターの相コイルは、丁度90度に直行した構造なので、X、Y軸の関係に等しい。
X、Y軸をそれぞれ、サインとコサインの関係で、電流比を制御すると、理論的には無限大まで分解能を上げられる。
このICはステップモーターの1ステップを128分割まででき、きめ細かな制御が出来る。
注意して欲しいのは、ステッピングモーターに流れる電流は、誘導性負荷なので、速度(回転速度)が速くなると当然流れる電流は少なくなる、なので、制御の精度は、「比」となる、絶対的な電流では無い。

実験には、ATMega88P を使い、SPI の制御を行って、L6470 にコマンドを送って制御した。
※ネットを探すと、Arduino のスケッチが色々見つかるが、純粋に C++ でプログラムを行った。

ステップモーターは秋月で買った物で、定格12Vなのだが、ユニポーラをバイポーラとして駆動する為、12Vで駆動する場合、定格の半分となる。
※最終的にはタコメーターを作る予定なので、12Vで丁度良いと思う。

回路図は書いて無いので、結線は以下のように接続する。
PC0: L6470 STBY/RST
PB2: L6470 /CS
MOSI PB3: L6470 SDI
MISO PB4: L6470 SDO
CLK PB5: L6470 CK

電源は12V、AVRには5Vを印加する。

※他の回路は参考資料にある回路をそのまま使う。
※boot ストラップに使うダイオードはショットキーダイオードを使う。(手持ちの都合で、2A/40V を使った)

IMG_0443s

ソースコード

まだ、実験的コードなので、色々未完成、タコメーターのハード製作に合わせて修正していく予定。

-----
追記:
2013年7月1日(21時25分)
ソースコードを更新しました。
spi.hpp ---> AVR の SPI 通信をクラス化したものです。(まだ不完全です)
L6470io.hpp ---> L6470 の制御をクラス化しました。(まだ不完全ですが、コマンド発行など、より判り易く使えます)

2013年7月1日(22時23分)
L6470io.hpp を更新、これで、アプリケーションから、必要な事が出来るようになった感じ。
※ステータスの読み込みは、まだ未実装

動画をアップ

HIDaspx を作る

AVR を使った電子工作では、ISP プログラマーが必須だ、以前から「ELM さんの AVR ライタ」を使わせてもらっている。
※シリアル→ISPの変換デバイスを USB シリアル変換(FTDI)と組み合わせて、ケースに入れてある。

IMG_0436s
※さすがにかなりくたびれているが、未だ現役。

数年前に、ATtiny2313 を使って、ソフトウェアーだけで、USB クライアントになる ISP プログラマーが発表され、ずっと使ってみたかったのだが、現役のプログラマーで何とかなっていたので、ずっと見送っていた。
千秋ゼミ(AVRマイコンなど)
先日ゲームコントローラーを作るので部品を買った余り(余分に買った)があり、丁度良い機会なので、作ってみる事にした。

HIDaspx は、ATtiny2313 といくつかの部品だけで動作する AVR 用 ISP プログラマーなのだが、非常に洗練されており、ソフトウェアーの機能もてんこ盛りだ(はまった)。
折角なので、単純に AVR の ISP プログラマーを作りたい人の為に、簡単な製作記を残しておこうと思う。

(1) ハードの製作
HIDaspxは、プリント基板を販売しているところがあり、それを使えば簡単なのだがー、いくつかの点で、自分のスタイルに会わない為、今回はパス。

・やはりこの手の物は、ちゃんとケースに入れて、使わないと、ショートしたり、何かとトラブルが起こるもので、前回同様、同じケースに入れる事とした。
・ISP のコネクターは、前回と互換性を持たせ、10ピン仕様とする。
・最近はミニ USB やマイクロ USB が主流で、小さくて、扱いやすいので、ミニ USB コネクター仕様とした。
・電源の切り替えは、前回同様、5V、3.3Vを切り替えできるようにした。
・HIDaspx は、Ready と Busy のランプがある。
以上を踏まえて、ケース内のレイアウトを決め、ケースの加工、基板の切り出し、加工を行った。

IMG_0434s
※この段階では、LED 電源切り替えスイッチの穴は開けてない。
※ミニ USB のコネクターは、既製品の変換アダプターを使った。(かなり昔に買ったもの)
※このケースは、底蓋の面構造が、基板を、コネクターの形状を利用して固定するのに都合良く作られている。

内部に納める基板が出来たら、回路図を見ながら、部品を乗せて行く。

IMG_0437s

IMG_0438s
※緑の LED、VF が 3.2~3.4V なので、3.3V で動作させた場合にLEDが点灯しないのでは無いかと考え、フォトカプラーを間に入れて、常に5Vで駆動するように改修してある。
※他は、ほぼオリジナルの回路準拠
※経験上、三端子レギュレーターの入力側にはコンデンサはを入れていない(無くても、まず発振したりしないし、レギュレーションが悪くなる事も無い)
※緑 LED は超高輝度を使った為(手持ちがそれしかなかった)、電流を少なくしても、直視できないほど刺すような明るさで、失敗・・・、ブルー LED に交換する予定

最後にケースにレタリング

IMG_0439s
※新旧、2台のISP

IMG_0440s
※USB コネクター部分の違い

(2) 動作検査
まず、ファームやツールなどをダウンロードする。

今回使ったのは「hidspx-2012-0326.zip」で、既存の ISP で、2313 にファームを書き込んだ。

hidspx-2012-0326/bin/firmware/main-12.hex

ヒューズの設定は、ここに詳しく書かれている。

さて、一応、配線を確認後、PC の USB ポートに接続する、しばし・・・
問題なく、デバイスドライバーがインストールされた。

※自分の環境は、Windows7 64 ビットだ。
※USB2.0 のハブに接続している。

HIDaspxDeviceMan
※デバイスドライバーは、「HID 準拠デバイス」として認識されているようだ・・

(3) ツールの用意

hidspx-2012-0326/bin/hidspx.exe があるので、cygwin などのシェルを起動して、このコマンドを使ってみる。

↓以下のようにコマンドを打てば、認識したデバイスを表示してくれる。
% ./hidspx -ph?
VID=16c0, PID=05df, [ YCIT], [HIDaspx], serial=[0000] (*)

では、AVR を ISP に接続して、ヒューズビットを読み込んでみよう~
% ./hidspx -ph -d4 -rf
Detected device is ATmega88.

==== ATmega88 ====

Low: 11100110
||||++++-- CKSEL[3:0] システムクロック選択
||++-- SUT[1:0] 起動時間
|+-- CKOUT (0:PB0にシステムクロックを出力)
+-- CKDIV8 クロック分周初期値 (1:1/1, 0:1/8)

High:11-11111
|||||+++-- BODLEVEL[2:0] (111:無, 110:1.8V, 101:2.7V, 100:4.3V)
||||+-- EESAVE (消去でEEPROMを 1:消去, 0:保持)
|||+-- WDTON (1:WDT通常動作, 0:WDT常時ON)
||+-- SPIEN (1:ISP禁止, 0:ISP許可) ※Parallel時のみ
|+-- DWEN (On-Chipデバッグ 1:無効, 0:有効)
+-- RSTDISBL (RESETピン 1:有効, 0:無効(PC6))

Ext: -----001
||+-- BOOTRST (1:Normal, 0:BootLoader)
++-- BOOTSZ[1:0] (11:128W, 10:256, 01:512, 00:1024)

Cal: 182

※何かの拍子に、ISP が認識しなくなる事があるようだ、USB ケーブルをさし直すと直るようだが・・・

----- 注意!注意!注意! -----

hidspx は非常に高機能で、複雑、オプションなど注意を要する、得に、他の ISP プログラマーの機能を取り込んでいる為、他の ISP と混同すると期待した動作はしない。

※ありがちな事(自分もやった)
・USBaspx と HIDaspx を混同する。
※USBaspx は、libusb-win32 経由でデバイスとやりとりする為、libusb-win32 のツールを使って、デバイスを認識させる必要がある。

・libusb-win32 のツールを使って、HIDaspx を認識させてしまうと、hidspx で認識しなくなる。
※誤って、そうしたら、デバイスドライバーを削除すれば OK。
※HIDaspx はドライバーのインストールなどが一切不要で、直ぐに利用できる、半面、デバイスドライバーのプロパティでは、「HID 準拠デバイス」としか認識されない。

「千秋ゼミ」のページは、情報が多く、初心者には判りずらいかもしれない、しかしながら HIDaspx は非常に素晴らしい ISP なので、AVR のプログラマーを探している人は是非使ったら良いと思う。

追記:
> 何かの拍子に、ISP が認識しなくなる事があるようだ、USB ケーブルをさし直すと直るようだが・・・
外部電源に、数百mA程度を消費するような機器を繋いだ場合に、瞬間的に電源電圧が変動して、ATtiny2313 の機能が止まって認識しなくなるようです。
※Windows のデバイスマネージャーからは、正常に見えている。
そこで、外部電源ラインに、直列にフェライトビーズ「BLM18PG471SN1D」(秋月電子扱い)を入れたら、安定しました。
※470オーム(100MHz)/1000mA/0.2オーム(直流抵抗)

AVRでUSBゲームコントローラーを自作する

自分フレームワークの実装過程で、前回、エミュレーターソースを流用してスペースインベーダーを作成した。

GLFW3 には、標準で、ゲームコントローラーの取得 API があるので、それを使ってみる事にした。

適当な USB ゲームパッドを買っても良いのだがー、備品の中に、ファミコン時代に購入した、「ASCII STICK II TURBO」が埃をかぶっている事を発見したー

IMG_0423s

内部
IMG_0424s
※ボタンの「受け」などは鉄板で補強してあり、筐体はプラスチックだが、必要な剛性を確保してある。
※設計は、当時ナムコの筐体デザインなどをしていた遠山さんと言う噂なのだがー、この創りの良さとこだわりは、実際そうなのだろうと思う。

当時5000円くらいはした覚えがあるが、機能的には、ファミコン用なので、ボタンが少ないのだがー、連射や、4方向、8方向切り替えなど、色々と芸が細かい、
デザインも優れているし、剛性も高く、方向レバーはマイクロスイッチで、価格以上の素晴らしい創りとなっている、今回はこれを改造する事とした。

USB のゲームコントローラーでは当然ながら、制御用マイコンが必要だが、ネットを探すと色々ある、今回利用させてもらったのは「ココ」のサイト。
AVR を使った USB ゲームデバイスで、最近ではあまり馴染みの無い ATMega8 を使っている。
手持ちの ATMega88 とかで出来れば良いのだがー、このコントローラーの制御ソースはアセンブラなので、ATMega8 を別途購入した(秋月で180円)。
そもそも、USB の通信機能が無い AVR でUSB(Low スピード)の通信を行うのはかなりハードルが高い、マシンサイクルを正確に計算して、ソフトウェアーのタイミングループだけで、USB の制御信号を
生成しているのは、クールとしか言いようが無い。

ASCII STICK には、何に使うか最後まで不明な「OPTION」コネクターがある。
丁度良いので、USB コネクターはここに出そうと考えて、工作してみた。

IMG_0429s
※回路は、特に変更はしていないが、USB の差動ラインに入っている68オーム、手持ちが無かったので75オームとした。
※アナログの電源ライン「GND」も回路図ではオープンとなっているが、一応 GND に落としておいた。

----- Fuse ビット書き込み時の注意 -----

ATMega8 は、ピンアサインなどは、ATMega88P などに近いものの、古いデバイスで、注意する点があります。
現在も流通はしているものの、ATMega88Pや上位デバイスに置き換えるのでしょう。
※自分もそれでハマリました・・・
まず、ヒューズビットを書き込む場合ですが、最初に low バイトを書き込んでしまうと、発振機が動作しなくなります。
これは、「fuse.txt」の値が適切とは言えない点と、ATMega8 の仕様によるところにあります。
12MHz のクリスタルを使う場合なので、high バイトの「CKOPT」を変更して、「0」にしておく必要があるようです。

僕は、色々やった結果、high バイトに「0xC9」、low バイトに「0xEF」を書き込んでいます。
※WatchDog は有効にした方が良いかもしれません・・
※fuse.txtでは、low バイトの CKSEL が、「External Low-frequency Crystal」となっていて、これは絶対間違っていると思います。

AVR の痛いところは、fuse の書き込みを失敗すると、再起不能になる場合がありますので、注意が必要です。
クロックの選択を誤った場合は復帰するチャンスはありますが、「RESET」端子とかを無効にしたりすると、ISP でのプログラムは不可能となります。

IMG_0431s

IMG_0433s
※いつものフォーレックスを適当に削って、USB コネクターのカバーにした、基板とLOCTITE で接着してある。

Game_Controler_Dialogjpg
※ゲームコントローラーとして認識したダイアログ(ボタンが多いのは愛嬌)

フレームワークを USB コントローラーに対応した。
ソース
バイナリー