hira のすべての投稿

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

(8)文字列の扱い(コンテナによるオブジェクトの受け渡し)
AVR の C++ には STL が無いので、std::string を使う事はできませんが、文字列を確保する簡単なコンテナを実装して、それを使う事は出来ると思います、またネットを探せば、std::string に似せた実装のいくつかを見つけられると思います。
※動的に確保が必要な文字列が必要無いのであれば、以下の項目はスルーして下さい、しかしながら、常に固定化された文字列だけ扱えば良い場合と言うのも稀と思います。
※std::string が使える環境で、あえて、同じような文字列のコンテナを使う意味は全くありません(ゲームの開発者などに多いようですが、std::string より優秀なクラスを設計して実装し、動作確認をするには、極めて莫大な時間と労力を消費すると思います)、std::string は極めて正しく実装されており、std::string より優れた実装を行う事は殆ど不可能に近いと思います、これは、STL を理解していない「一知半解」と言うべき愚かな行為です。
※メモリーの断片化を避けて、独自の記憶割り当てを行いたい為に、独自の string クラスを作る人がいますが、そんな事をしなくても、アロケーターを実装して、それを渡せば良いのです、std::string が仕様的に不適格で、別の実装を考えなければならない場合は通常殆どありません。
C 言語から C++ を始めて間もない頃は、文字列をやりとりする場合などに、std::string と char* をチャンポンで使った実装をする事が多いと思います、ですがー、コンテナを使った方法論に移行すべきタイミングです。
コンテナを使って返す事で、余分にコピーが発生するので、速度が落ちると考えるかもしれませんが、最適化以前に、より良い設計を心がけるべきで、最適化はその後です。
※通常、コンテナの受け渡しは参照によって行われるので、コピーが発生するのは、最小限となります。
※ AVR の gcc では std::string が使えないので、勉強と、STL 理解の目的で、string クラスを実装してみるのは有益だと思います、仕様は、std::string を元にすれば良いし、テンプレートの作り方などを習得できます、同じ物を作るのはハードルが高いので、自分に必要な機能だけ実装それば良いと思います。
実際には、AVR では、メモリー領域が物理的に分かれている為、ROM 上(コード領域)の場合と、RAM 上の場合で、別々の処理を行う必要があるので、簡単では無いのですが・・

class aaa {
  // 内部で扱うコンテナや変数は「private」にし、外部に公開する場合は、必ずアクセサーを使ってアクセスするように設計します。
  std::string  text_;
public:

  // const 参照で受けて・・
  void put(const std::string& text) {
    text_ = text;
  }

  // const 参照を返す・・
  const std::string& get() const { return text_; }

  // ポインターを返す仕様なら、条件が「偽」なら、0(NULL ポインター)を返せば、シンプルと思うかもしれませんが、ソースがコンテナであるのに、そのポインターを返すのでは二度手間になる場合があります、やはり、統一的にコンテナとして扱えた方が利便性が高いので、「偽」の場合に、空のコンテナを返すようにすれば、コンテナとして統一的に扱えます。
  const std::string& test(char ch) const {
    if(!text_.empty() && text_[0] == ch) {
      return text_;
    } else {
      static std::string empty;
      return empty;
    }
  }

  // 固定の文字列を返すのなら「const char*」で返すのは問題無いでしょう。
  const char* name() const { return "aaa"; }
};


int main() {
  aaa a;

  a.put("abcdefg");  ///< std::string は、「=」オペレーターで「char*」型も受け取れるように実装されている。

  std::string b("qwert");
  a.put(b);          ///< もちろん、std::string を受け取る事も出来る。

  {
    const std::string& t = a.get();
    if(!t.empty()) {
    
    }
  }

  {
    const std::string& t = a.test('a');
    if(t.empty()) {

    } else {

    }
  }
}

 
 
(9)avr-g++ の制限と回避
avr-g++ は、stdc++ ライブラリーが無い為、通常標準で備わっている事が出来ませんので、それら使う場合は個別に実装する必要があるようです。
・new、delete

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

void operator delete(void * ptr)
{
    free(ptr);
}

 
・また場合によっては、スレッドセーフの機構を呼び出すコードが追加されますので、以下のコードも追加します。

__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++ のコードをコンパイル出来るハズです。

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

(5)define
C では、例外無く、普通に define を使ってきました、しかしながら、多くのプログラミングガイドなどで、害悪が指摘されるように、C++ では define を使わなくても良い方法が提供されています。
人によっては、少しだけ注意すれば便利なので、それ程とやかく言わなくても的な事を言う人もいますが、define より安全で、優れた機能が提供されているのに、何故、危険で制限のある古い方法を使うのか理解に苦しみます。
※厳密には define を使わないと、どうしても解決出来ない事が全く無い訳では無いのですが、それは、「稀」な事と思います、普通は、使わなくても何とかなる事の方が多いハズです。
※「郷に入れば郷に従え」

C では、define をアセンブラのマクロ命令のように、人間による最適化を施した拡張命令のように捉えられていると思われますが、「最適化」はいくつかの例外を除いて、人間がやるよりコンパイラに任せた方が安全で確実であると思います。
※精妙な最適化は、それが必要になったら行うべき事項であり、開発途中の中途半端な段階で行うのは問題が多く実りが少ないと言えます。

C++ では、最大の最適化でも、「関数の呼び出し」を尊重してコンパイルします、その関数が少ないステップで構成されている場合でも、指示の無い事は行いません。
もし、設計者が、ステップ数や、実行時間の比率などを考えて、関数を展開した方が有益と判断されるなら、「inline」を宣言する事で、その関数は展開され、呼び出しのコストが取り除かれます、ただ、注意しなければならない事があります、どんな場合でも「展開」する事が有益だとは限らない事です、たとえばキャッシュが小さいマイコンの場合は、展開する事で、キャッシュのヒット率が悪くなり、逆に速度が落ちるかもしれません、結局、計測してみないと本当の事は判らないと言う事です。
※もちろん、キャッシュが無いような小規模なマイコンでは、当てはまりません。
※「計測」は非常にスキルの必要なエンジニアリングです、浅はかな計測では大抵真実は明らかになりません。

組み込みで、良く使われる define の利用法として、ハードウェアーの制御や定数の定義などがあります。

たとえば、ボーレートの初期値として・・・

#define BAUD_RATE 9600

これは、単純に

static const int BAUD_RATE = 9600;
又は、
enum rate {
  BAUD = 9600,
};

のように、名前空間などを利用した構造的な定義が出来ます。
最適化されれば、余分なメモリーを消費する事も無く、参照されずに直接アセンブラ命令に埋め込まれます。
※この場合の定数は、「int」型となりますので、int 型が都合悪ければキャストする必要があります。

良くありがちな I/O デバイスの操作で・・

void write_data(u8 data) {
  ACTIVE_PORT;    // ポートを有効
  OUT_DATA(data); // データ出力
  WRITE;          // ライト
  CS_LOW;         // CS 有効
  SETUP_DELAY;    // データのセットアップタイム
  CS_HIGH;        // CS 無効
  INACTIVE_PORT;  // ポートを無効
}

↑のように、あるデバイスにデータを出力する手続きを実装したものです。
※本来、大文字で書かれた命令は、普通に関数(inline 関数など)を定義しても良いのですが、内部で行っている事が、少なく、関数を定義するのが「冗長」又は、マクロ展開で処理コストを節約とかの判断なのでしょう、典型的な C のプログラマーは、これを define で定義する事が多いように思います。
※また、define では、パラメーターを埋め込んで、適当に整形して展開出来る為、かなり複雑な事も可能ですが、C++ では殆どの場合、テンプレート関数でそれを置き換える事が出来ると思います。

#define ACTIVE_PORT { ... }
これは、単純に・・
inline void ACTIVE_PORT() { ... }

#define OUT_DATA(d) { ... }
引数がある場合は、その引数の「型」を定義できるので、正確で安全です。
inline void OUT_DATA(uint8_t d) { ... } 

 
次の例では、一次元配列に対して、二次元配列的にアクセスするマクロです、型に依存しない様にテンプレートで表現しています、define を使わなくても可能な例です。
※配列サイズを超えたら、例外を投げるとか、色々拡張する事もできるでしょう。
※参照を使っているので、関数に直接値を代入するような書き方が可能です。

/// 読み出し専用
template <typename T>
const T& get_dim_(const T* p, uint32_t row, uint32_t col) {
    return p[(col << 2) + row];
}

/// 書き込み専用
template <typename T>
T& put_dim_(T* p, uint32_t row, uint32_t col) {
    return p[(col << 2) + row];
}
-----
    float mat[16];
    
    put_dim_(mat, 1, 3) = 10.0f;

    float a = get_dim_(mat, 1, 3);

 
 
(6)キャスト
C では、「キャスト」について、アセンブラ的に、単純に警告を取り除く為の手段として利用しているのが殆どのように思います。
C のキャストはたとえば、こんな感じの物です。

  char ch;
  ...
  unsigned char data = (unsigned char)ch;

C++ では、いくつかのキャストが用意されており、場合によって使い分けます。
  unsigned char data = static_cast<unsigned char>(ch);

良く、C++ のプログラムで、C のキャストを使う人がいますが、C のキャストは、変換が可能か不可能かに関係なくエラー検査無しに変換します。
※驚く事に、「C++ のキャストは冗長だから、C のキャストを使う」と言っている人がいましたが、反面教師にすべきです。
C++ では、static_cast を良く使いますが、もし変換が出来ない場合はエラーとなります。
C++ のプログラムでは、C のキャストを使わないようにして下さい。
また、const を取り除くようなキャストも行ってはなりません、そのような操作は、基本的に間違っています、通常、設計を正しくする事で避ける事が出来ます。
※ C++ の他のキャストについては、リファレンスを参照して下さい。
 
 
(7)警告
コンパイラが出力するレポートは非常に重要です、エラー検査は最大レベル(厳しいエラー検査)を設定し、一つでも警告が出たら、実行する前に、警告を取り除くようにソースコード修正する「クセ」をつける必要があると思います。
良くありがちな事として、「警告は最後にまとめて取り除く」という方針を実践している人がいますが、間違いです、「警告」が出るのには理由があり、コンパイラがソースコードの構造的欠陥を指摘しているのですから、警告が出ないような、抜本的な改修や、綺麗な設計が必要です、これらの優先順位は最後に回すべきものではなく、「今」行うべき事項であると認識して下さい。
※とりあえず「警告」を無視する事の無いように・・・
※「警告」が沢山出るプログラムは、そもそも設計が良くない場合も考えられます。

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

(4)クラス
「クラス」は、C++では、大きなトピックです、ここでは、クラスに関連するいくつかの事を紹介します。

・初期化リスト
「クラス」では、「コンストラクター」と呼ばれる特別のメソッドがあります。

class bitmap
{
public:
// コンストラクター
 bitmap() { }
};

これは、ご承知の通りです。
普通、コンストラクター内では、変数の初期化を行います。

class bitmap
{
  int counter_;
public:
// コンストラクター
 bitmap() { counter_ = 0; }
};

「= 0」と値を代入しています。
これは、間違いでは無いのですが、「=」(イコール)で代入するのでは無く、コンストラクターでは、「初期化リスト」を使います。
違いは、初期化リストでは、各オブジェクトのコンストラクターを呼んでいるのに対して、代入では、=オペレーターを呼んでいる事になります、最適化された場合は、殆ど同じになりますが、コンストラクター内では、初期化リストで初期化するようにして下さい。
※詳細な理由については、記しませんので、ご自分で調べて下さい。

class bitmap
{
  int counter_;
public:
  bitmap() : counter_(0) { }
}

・メンバー変数に「_」アンダースコアーを付ける
クラス内のメンバー変数は、引数の変数名などと被らないようにします。
典型的には、「m_counter」などとする事もありますが、これは、ハンガリアンスタイルと言えます、なので、シンプルに後ろに付けるのが好ましいと思えます。
※高橋晶さんから指摘してもらいましたので修正します。
先頭アンダースコアの次に大文字が来る場合は規約違反となります。

※インクルードガードにアンダースコアー
多くの人(C のプログラマーに多い)が、インクルードガードで使うキーワードに、未だにアンダースコアーを使っている人がいます。

func.h の場合
#ifndef __FUNC_H__
#define __FUNC_H__

...

#endif

しかしこれは、規約違反である事を念のため確認しておきます。
※私は、「#pragma once」をお勧めしますが・・

func.h の場合
#ifndef FUNC_H
#define FUNC_H

...

#endif

・引数の void
受け取るパラメーターが無い場合、C では void を使いました。

void init(void)
{
}

C++ では、何も書く必要は無くなりましたので、引数が無ければ何も書きません。

void init()
{
}

※しかし、書いてもエラーにはなりません、問題なのは、書く必要が無いのにあえて書く人と、その心理でしょうか・・・

・引数に標準的な値を代入できる
受け取るパラメーターがあったとして、標準的な値を代入しておく事が出来ます。

void set(int value = 1);

...

  set();     //「1」が引数として使われる。
  set(100);  //「100」が引数として使われる。

これらを利用して、色々と便利な事が行えます、応用してみて下さい。
私がお勧めしたい応用として、ブーリアンを使った、フラグの設定を紹介します。
よく、状態として、「許可」と「不許可」を設定したい場合があります。
そんな時・・・

void enable();
void disable();


のようにしますか?
ですが、これだと、何かの状態を評価してから、状態を設定する場合、関数を呼び分けなければなりません。

if(flag) enable();
else disable();

そこで・・

void enable(bool f = true)
{
}

とすればー

  enable();  // 許可したい場合

...

  enable(false);  // 不許可したい場合

...

  enable(flag);  // flag が「true」か「false」で、「許可」、「不許可」を設定できる。

※よく、二つの状態を受け渡しするのに、「int」とかを使う人がいますが、それは間違いです「bool」を使って下さい。
※又、3つなら「int」が便利(1, 0, -1)と言う人がいますが、それも間違いで、3つの状態があるなら、enum などで、3つの状態を定義して、それを使います。

・enum の便利な使い方

C++ では、define を使わなくなりますし、あえて使う理由もありません、そこで定数を定義するのに便利な enum を C++ で便利に使う為の方法を紹介します。

enum は意味のある値を定義する上で便利な機能ですが、不都合な事が起こります。

たとえば、以下のような、enum の定義では、enum 内に同じ名前のキーワードを定義できません。

enum holizontal {
  LEFT,
  H_CENTER,   // これは少し冗長
  RIGHT
};

enum vertical {
  TOP,
  V_CENTER,   // これは少し冗長
  BOTTOM
};

しかし、C++では、こう書けば・・

struct holizontal {
  enum type {
    LEFT,
    CENTER,
    RIGHT
  };
};

struct vertical {
  enum type {
    TOP,
    CENTER,
    BOTTOM
  };
};

型名をクラス名にして、その中で enum を定義する事で、別々に定義出来ます。
※名前空間で分離する事も出来ますが、私は、クラスで括る方が好みです。
※私は、このような場合に enum の型として type と言うキーワードを使うのが好みです。

int main()
{
  holizontal::type h = holizontal::CENTER;
  vertical::type v = vertical::CENTER;

...

}

※structとclassの違いについて
C++ では、struct と class の違いは、殆どありません、private か、public の違いくらいです。
※ main 関数の戻り値
↑のサンプルでは、main 関数の戻り値(return 0;)が記述されていません、C++ では、main 関数の戻り値が無い場合、0が戻る事が保障されています、これは言語規約に定められています、言いたいのは、C++ では、C 以上に言語規約を良く理解して正確に従う必要がある点です。

・new、delete をなるべく使わない
クラスを定義して、それを使う場合、以前に良く見たサンプルは、以下のような物です。

  func* f = new func;

  f->xxx();

  delete f;

しかし、このように書けば、new、delete を省略出来ます。

  {
    func f;

    f.xxx();

  }

↑のように書けば、delete を忘れて、メモリーリークする事を防げますし、func クラスが生成されるタイミングもスコープで制御できます。
雑多な事はコンパイラに任せ、コンパイラに出来ない事に集中します、最適化にも貢献出来ます。

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

C++を常用的に使うようになってから随分時間もたち、C++も少しだけ上達してきました。
最近では殆どのプログラムをC++で行うようになり、組み込みのような小規模なシステムでも、開発環境が提供される限りC++を使っています。

話は変わって、AVRマイコンの開発環境で「WinAVR」と言うのがあります、gcc が使えるフリーのツールキットなのですが、8ビットマイコンであるにも関わらず、C、C++でプログラミングできるフリーの開発ツールです、但し、C++のプログラミングでは、STLは使えないのですが、まぁ、数百バイトのRAMや、数キロバイトのROMでは、リソースの関係で困難と思えます、まして、データ領域とプログラム領域が分離しているアーキテクチャーでは、物理的に同じような実装をする事は難しいのかもしれません。
ただ、string、vector、map のようなコンテナは仕様に制限を設定すれば、実装する事は可能でしょう。

AVRで使える g++ は非常に良く出来ていて、最近では、AVRのプログラミングでもC++でプログラムするようになりました、何か特別な理由が無い限り、Cでプログラムを書く理由は無いと思えます。

しかしながら、典型的なCのプログラマーは、多くの勘違いをしている事が多いと思います、これはC++を本格的に学ぶ前の自分もそうでした。

(1)C++はリソースを多く消費し、プログラムサイズが大きくなる。
(2)C++は余分な処理が多く、余分なマシンサイクルを消費するので、遅い。
(3)Cのプログラミングスキルは、C++にそのまま生かせる。

まず(1)ですが、幾つかのプログラムをつくり、最適化されたアセンブラコードを観てみましたが、適正に書かれたC++コードは、Cのコードと遜色は無く、余分な命令は殆ど追加されていませんでした。
※これは処理系によっては、違う結果となる場合もありますが、WinAVR g++ ではそんな事は無く、洗練されたアセンブリコードが出るようです。

次に(2)ですが、(1)で示したように、余分な命令は殆ど無いので、余分なマシンサイクルも消費しません、場合によっては、より最適化されたコードが出る可能性も含まれている為、C言語より高速なコードが出る可能性もあります。
但し、「適正に書かれた」と言う前提が付きます、適正に書くには「学ぶ」必要がある為、C++に不慣れなプログラマーが、少しのC++のスキルを使って検証したら、別の結論に行き着く可能性があります。

そして(3)です、確かに、何かの処理をどのように細分化して、実際に動くコードを作成する能力(基本的なプログラミングのスキル)は、生かされるのですが、C++には、Cに無い様々な用法があります、それを知らないと、適切なコーディングは出来ないと思うのです、しかしながら典型的なCのプログラマーは、C++をある程度理解しているし、実際に動くコードを実装出来るので、使うつもりなら、いつでもC++でプログラミング出来る(C++は大体理解している)と思っています、しかし、C++はCに似ていますが、全く違う言語である事を認識すべきでは無いでしょうか、C++で適正なプログラミングをする為には、多くの時間を使って学ぶ必要があると思います、もしC++を理解していれば、Cより便利で安全なC++が使える環境でC++でプログラミングしない理由は無いハズなのです、C++を使わないのはそれ相応の理由が存在するのでしょう。
※幾つかの場面で、C++でプログラムしない理由は存在しますが、本当にそうなのかは検証しなければ判らないし、ケースバイケースです、Cを選択するのが最適な場合はあるででしょうが・・

これから、不定期に何回かに分けて、C++でプログラムするのに便利なテクニックや豆知識を紹介していこうと思います。
主に、昔に感じた事や重要と思われるトピックを中心に書いていこうと思います。
この文書は、正確でなかったり、実際と違う場合などがあると思います、気が付いたり指摘されれば修正していこうと思います。

良く、初心者が読むのに適したC++の参考書は?と聞かれますので、とりあえず、この本をあげておきます。
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス

・名前空間

Cには無い便利な機能として「名前空間」があります、名前空間を活用する事で、プログラムを判りやすく、構造的にする事が出来ます。
※悪名高い Embedded C++ には名前空間がありませんが、C++を理解出来なかった人たち(日本人です)によって策定された仕様です、恥ずかしい事です。

・参照

「参照」は、C++でも最も利益のある機能の一つだと考えます、参照はポインターに似ている為、Cのプログラマーは軽視しがちですが、コンパイラーにとっては、最適化を進める上で、非常に強力な武器です、関数に渡すのがポインターでは無く、参照であれば、より進んだ最適化を行う可能性が生まれます。
また、参照では、ポインターのような、NULLチェックを行う必要は無く、構造的に参照が適用出来ない場合は、コンパイラが教えてくれます。
ポインターより少しだけ制限のある参照は、より洗練された構造をプログラムに提供し、それと同時に安全性も提供します。
参照では、const をより明確に使え、明確な意図をもって伝播させる事が出来ます、これを最初は「ウザイ」と思う人もいますが、そうでは無い事は直に理解出来ると思います。
一つの典型的な方法論として、まず参照で解決出来るか考えて、なるべく参照を使うように全体を設計し、どうしても参照に出来ない場合だけ、ポインターを使うようにします。

ポイント:
NULL について:
「NULL」はC言語のマクロであり、C++でも使えますが使うべきでは無いと考えていますし、使う理由もありません。
C++では「0」を使えば良いのです、NULLを使う理由として、数値に代入する0と、ポインターに代入する0を明確に分けたいとの理由を挙げる人もいますが、それが、わざわざ、C言語のマクロを使う理由としてメリットがあるとは考えられません、また、NULLマクロを使うには stdlib.h(cstdlib)をインクルードする必要があります。

・基本的な事

(1)標準ライブラリーのインクルード
C言語のヘッダーをインクルードする際に
C++では、C言語で使える関数も当然使えます、その際ヘッダーをインクルードしますが、C++用に専用ヘッダーが用意されています。

たとえば、「stdio.h」なら「cstdio」、「stdlib.h」なら「cstdlib」、「string.h」なら「cstring」です。
※規則は察しがつくと思います。
C++の標準的ヘッダーは、「.h」などの拡張子が無いので、それに習っているのと、C++から使う際の「おまじない」がしてあります。
※AVR の g++ では、C++ 専用のヘッダーが用意されていない為、普通のCヘッダーをインクルードします。

(2)型について
C++では、「型」を厳密に評価します。

Cの場合、たとえばポインターは典型的に以下のように書きます。

void func(char *ptr)
{
  char *tmp = NULL;

...


}

しかしC++では・・

void func(char* ptr)
{
  char* tmp = 0;

...


}

ポインターを示す「*」が、変数名に付いていたのが、型に付くようになっています。
Cでは、「ptr」の「ポインター」、「tmp」の「ポインター」だったのが、
C++では、「ptr」や「tmp」は「char」の「ポインター型」と言う考えによるものです。
しかし、多くの人が、自分流の定型記述セオリーを持っており、少しでも異なると、「気持ち悪い」と感じる為、それだけでも、テンションが下がる要因になる場合も少なくありません。
これは、慣れの問題で、もちろんコンパイラーは、「char *ptr」でも「char* ptr」でもエラー無くコンパイル出来ますが、しばらくは、自己流の狭い考えを捨てて、流れに身を任す事が寛容と考えます。

ただ、ここで問題が起こります。

  char* tmp, ptr;

このように書くと、「tmp」は「ポインター型」ですが、「ptr」は「char型」です、これは、C言語との互換を考慮して、このような不都合な事が起こります。
なので、一つの方法として、コンマで区切って、複数の変数を宣言しない事で避ける事ができます。

(3)スコープを利用した宣言

以前、Cの典型的関数では、関数内で使う変数を、頭の方で、集中的に宣言していました。

void func(void)
{
  int i, j, k;
  char c;

...


}

しかし、C++では、変数を使いたい時に、初めて変数を宣言できます。

void func()
{
  int i;
  int j;

...

  char c;
  int k;

...


}

また、スコープを使って、分離する事で、同じ変数名を何度でも宣言できます。

void func()
{
  int j;
  {
    int i;

...

  }

  {
    int i;
    int j;

...

  }
}

これは、コードがより観やすくなるだけでは無く、関数内であってもモジュール化でき、最適化に貢献できます。
ただ、↑の例で、スコープで囲まれた変数「j」を、大域の「j」と混同してしまう場合があり、注意する必要があります。※警告により回避出来ます
その都度宣言する事で、不必要なコードが生成されると思っている人がいますが、最適化されたコードは、そのような事はありません。(コンパイラーの常識、プログラマーの非常識)

OpenGL(with GLFW3)オーディオプレイヤー(ベータ)

OpenGL GLFW3 を使ったオーディオプレイヤーを作ってみました。

まだ、最低限の機能しかありません。

これは、OpenGL で GUI のフレームワークを実現する為のテスト的アプリケーションです。

GUI のフレームワークを作るにしても、何か、アプリケーションを作ってみない事には、必要な要件や仕様などが判らない為です。
GLFW3 を使っているので、Linux や、OS-X でも動作可能と思いますが、現状では Windows 依存のコードが含まれている為、コンパイル出来ません・・

player

・mp3, wav 形式のファイルを再生するアプリケーションです。
・mp3 タグ情報の表示などを行います。
・描画は OpenGL で行っています。
・フォントの描画では、FreeType2 を使っています。
・cygwin の i686-w64-mingw32 クロスコンパイラを使っています。
・左下隅のボタンを押すと、ファイル選択ダイアログが開きます、音楽ファイルを選択して下さい。
・まだベータ版で予期しない動作で落ちる場合もあります(表面的なテストしかされていません)。
・ソースコードは、少し整理してから公開する予定ですが、「今欲しい」方はリクエストを」下さい、方法を考えます。

オーディオプレイヤー

※9月8日15時23分
・大きなメモリーリークを発見したので修正、アーカイブのリンクを修正

※9月9日22時47分
・aac (mp4a) のデコードを追加、タグ情報の表示
・FAAD2 ライブラリーの libmp4ff には、カバー情報(アルバムのアートワーク)を正しく扱えないバグがあり、修正しました。
・その他、微妙に修正

※9月10日22時
GitHub にソースコード一式を公開しました。

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」がその典型例です、しかしながら、規約では、16ビットや32ビットの定義は処理系依存であったと思います。
又、エンディアンが変わった場合も少なくと 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