RX65N Envision Kit で実現するオーディオプレイヤー

I2Cの実装が、まともになったので、タッチパネルの操作を反映して、ファイラー
にも対応した。
これで、画面タッチでファイルを選択して、音楽を再生するガジェットがまともにな
った。

また、曲の再生では、一時停止や、次の曲などをタッチ操作対応にした。

本来ボタンを描画して、GUIで操作させるべきなのかもしれないが、それは、又後
で考える。
GUIに関しては、このボードの正規なサンプルプログラムとして既に公開されている
が(SEGGER)、GUIのような複雑な動作をCで書くのは、自分的にはありえない。
また、GUIの見た目や、操作の動作をエミュレーションする環境も必要な事を考える
と、少し複雑なGUIを作ろうとすると、この環境だけではかなり厳しいと思う。

以前に実装したリソースとして、Windows、OS-X で動作する Open-GL ベースの GUI
フレームワークがあるので、それをマイコン用にダイエットしてポートする予定でいる。
その時に、PC上で、見た目やリソースを作成するアプリなども作る予定でいる。
※その前にDRW2Dのエンジンを何とかしないと駄目なんだけど・・・
※DRW2Dエンジンの操作は、他のデバイスとは異なっていて、ライブラリーで提供
される、また、このエンジンに関係すると思われるレジスター群は、読み出し専用か、
書き込み専用レジスターとなっており、詳しい説明は省略され、ライブライーを使う事
を前提としている。

話がそれたが、オーディオプレイヤーのタッチ操作は以下のようになっている。
- 3点タッチ(離れた時)で、ファイラーが有効になる。
- 上下のドラッグで、ファイルフォーカス
- 右ドラッグでファイル選択(ディレクトリーの場合、そのディレクトリーへ移動)
- 左ドラッグで、一つ手前のディレクトリーへ移動
- 再生中、右へドラッグで次の曲
- 再生中、左へドラッグでリプレイ
- 再生中、2点タッチ(離れた時)で一時停止
- 再生中、3点タッチ(離れた時)で再生中断
- 再生中は、曲の再生が終了したら、次の曲を再生

現在、MP3、WAV形式のファイルをサポートしてあり、タグの表示にも対応してい
る。
タグは、ID3V2に対応しているが、V1タグは過去の物であり、あっても無視され
る。
タグは、曲名、タイトル、アーティスト、トラック、リリース日付、などの対応になっ
ている。
※WAV形式のタグも同じように対応している。

ジャケット画像をサポートする予定で、JPEGライブラリーをRX用にポートしたが、
一般的な方法では、メモリ不足で、デコードする事が出来ないので、こちらはそのうち
対応する事にする。(多分、一時メモリを使わず、直接フレームバッファに描画するよ
うにすれば可能なハズだが、リアルタイムにスケーリングもする必要があり、少しハー
ドルが高い)

オーディオ出力は、RX65NのDA0、DA1から出力する(量子化は12ビットだ
が)、無音は、3.3VとGNDの中間電圧、1.65Vとなっている。
バイパスコンデンサと抵抗だけで直にラインに繋げるのは心苦しいので、自分は以下の
回路を組んでいる。


※アンプの参考回路(以前にRL78で実装したWAVプレイヤーのもの)

量子化は12ビットだが、自分の耳では、そんなに悪い音に感じない。
※ブラインドテストで16ビットとの違いを聞き分けるのは難しいと思う。

現在DMACでD/Aに出力している部分をRSPIに変更して、多少改造すれば、外
部D/Aで鳴らせると思う。(いずれ、それも対応する予定でいる)
※SCIを使った簡易SPIだと、クロックを高速に出来ないので難しいかもしれない。
RX64Mだと、I2Sインターフェースがあるので、もっと簡単なのだが・・・
※自作音楽プレイヤーを前から作りたかったので、丁度良い。

GitHub AUDIO sample

RXマイコンSCIにおける簡易I2Cを実装する

SCIで簡易I2Cを実装している。

前回、ポーリングによる実装を行い、動作する事を確認した。
動作周波数が遅いCPUなら、それでも良いのだが、120MHzで動作するRXマイコン
だと、話が変わってくる。
さすがに、100KBPS程度の通信速度をポーリングで行うのは、マシンサイクルを無駄
に消費してしまうので、許容できないと思う。
そこで、割り込み処理にするのだが、RXマイコンで、I2Cを本格的に扱うのは今回が初
めてとも言えるので、あまりよく考えていなかった。
※IICAの専用インターフェースの実装も、いまだにポーリングのみの実装になっている。
これもそのうち手を入れないといけない・・・

SCIの簡易I2Cでは、STI(I2Cアクノリッジ待ち)で、送信終了割り込みを使う
のだが、「sci.hpp」に定義をしていない。
定義を追加するのに、しばし問題があった、送信終了割り込みは、グループ割り込みとなっ
ていて、SCIのチャネルによってグループが異なっていたり、RX64MとRX65Nで
異なっていたりと、場合別けをしなければならない・・

グループベクターは、icu.hpp に定義をしているが、グループが異なると、全く別の型とな
るので、そのままでは、定義が出来ない。
そこで、SCI の基底クラスを継承させ、チャネルでグループが異なる場合を別けて実装する
ように修正した。

SCI定義:

typedef scig_t<0x0008A000, peripheral::SCI0, ICU::VECTOR::TXI0, ICU::VECTOR::RXI0, ICU::VECTOR_BL0::TEI0> SCI0;

 typedef scig2_t<0x0008A100, peripheral::SCI8, ICU::VECTOR::TXI8, ICU::VECTOR::RXI8, ICU::VECTOR_BL1::TEI8> SCI8;

※SCI1 の TE0 はグループ BL0 だが、SCI8 はグループ BL1 になっている。

クラス定義:

template <uint32_t base, peripheral t, ICU::VECTOR txv, ICU::VECTOR rxv, ICU::VECTOR_BL0 tev> struct scig_t : sci_t {

};

template <uint32_t base, peripheral per, ICU::VECTOR txv, ICU::VECTOR rxv, ICU::VECTOR_BL1 tev> struct scig2_t : sci_t {

};

※基底クラス sci_t は、そのままに、グループ別に異なったクラスを定義した。

また、SCI を使った簡易 I2C は、SCI の制御と根本的に異なるので、純粋な SCI の制御と
クラスを別け、新規に「sci_i2c_io.hpp」とした。
※RXマイコンのソースコードは、第三者を交えたレビューを行っていないので、意外と、
基本的な部分に問題がある事が多い・・・
※多分、コードレビューを行うと、かなり厳しく突っ込みを入れられる部分があるものと思
う、コードレビューを経験した人なら解ると思うが、最初は、厳しい駄目だしをされると、
不快に思ってしまう、だが、人間「慣れ」るもので、回数を重ねると、ありがたく思うよう
になり、真摯に助言を受け入れる度量と分析力が育つ。

よく、趣味でマイコンをやっている人で、コードが汚いので公開をしない人がいるが、他の
人に添削してもらえる可能性があるのなら、是非自分のコードを見てもらい、正しい道に修
正してもらった方が良い。(くれぐれも、指摘された事を自分に対する「否定」と思わない
事、冷静に何が最善なのかを考える事)
プロジェクトだと、他人に駄目だしされる事を不快に思う人種はいて、リーダーの手腕が重
要となるのだが・・

ポーリング動作で、全体の見通しがついていたので、割り込み動作に関しては、それなりに
順調に実装できた。
FT5206、I2Cの定義は以下のように行う。
※FT5206のリセットを制御するので、ポートを定義。

// FT5206, SCI6 簡易 I2C 定義
typedef device::PORT<device::PORT0, device::bitpos::B7> FT5206_RESET;
typedef utils::fixed_fifo<uint8_t, 64> RECV6_BUFF;
typedef utils::fixed_fifo<uint8_t, 64> SEND6_BUFF;
typedef device::sci_i2c_io<device::SCI6, RECV6_BUFF, SEND6_BUFF, device::port_map::option::FIRST_I2C> FT5206_I2C;
FT5206_I2C  ft5206_i2c_;
typedef chip::FT5206<FT5206_I2C> FT5206;

割り込みで処理するのは簡単で、以下のように、I2Cのインスタンスを起動する時に、割
り込みレベルを設定するだけで良い。(ポーリングの場合は「0」を設定)

{  // FT5206 touch screen controller
    FT5206::reset<FT5206_RESET>();
    uint8_t intr_lvl = 1;
    if(!ft5206_i2c_.start(FT5206_I2C::SPEED::STANDARD, intr_lvl)) {
        utils::format("FT5206 I2C Start Fail...\n");
    }
    if(!ft5206_.start()) {
        utils::format("FT5206 Start Fail...\n");
    }
}

割り込みでは、データ転送が非同期になるので、同期を行うメソッドを追加し、非同期にデ
ータが来ても誤動作しないように、FT5206の実装も見直した。

sci_i2c_io.hpp
FT5206.hpp

KIT-RX65N、AUDIOプレイヤー更新

ファイラーがそれなりに動くようになったので、オーディオプレイヤーを更新、
PCMファイルのサポートも追加した。

操作は、ファミコンパッドで行うのだが、もうそろそろ、タッチスクリーンを有効
にしないと駄目な感じだ・・・
タッチスクリーンで使用しているICはFT5206という物らしい。

I2Cは、専用ポートしか考えていなかったので、専用のドライバーが既に動作し
ているが、FT5206はSCI6に接続されている。
なので、SCIの簡易I2Cを実装するしかない。
一応、ハードウェアーマニュアルを見て、実装してみたけど動作しない・・・
※ルネサス提供のドライバーは設定が面倒で、使いたくないし、読みにくいので参
考にするのも億劫だ。

割り込みでやるのは、設定が複雑になり、ハードルが高いと思ったので、とりあえ
ずポーリングでやってみたが、どうにも動作しない。
波形を見たいところだが、このボードでは、プローブを接続するのが難しいので、
他のボードで確認してみる事にしようか・・・
と思ったけど、ソフトI2Cで動作の確認だけしてみたー
動いたー(当たり前だけど)
※最初、動かないなぁーって思ったけど、とあるレジスターの応答が自分が思って
たのと違うだけで、ちゃんと動作してた(最近、こんな、早とちり的な事が多い)

ソフトI2Cで動作し、FT5206のマネージメントもそれなりになったので、
SCIの簡易I2Cをもう一度詳細に確認。
どうも、ポーリングの場合、受信動作は問題無いが、送信動作では、送信レジスタ
ーがバッファされているので、フラグを見る場合は、多少の工夫が必要な事が判っ
た。

while(SCI::SSR.TDRE() == 0) {
    sleep_();
}
while(SCI::SSR.TEND() == 0) {
    sleep_();
}

それを修正して動作するのかと思ったが、受信動作がどうも、うまくない・・・
よくよくレジスターのダンプを見ると、1バイトずれて読めてるような・・・
ルネサスのハードウェアーマニュアルの説明に沿って実装したものの、どうやら
本家の説明に間違いがあるようだー、SCIをI2Cとして使う場合、送信と受
信は連結されている、I2Cのスレーブアドレスを送信後に、受信レジスタを空
読みしないと、スレーブアドレスが残っている。

SCI::TDR = (adr << 1) | 1;  // R/W = 1 (read)
while(SCI::SSR.TDRE() == 0) {
    sleep_();
}
while(SCI::SSR.TEND() == 0) {
    sleep_();
}
volatile uint8_t tmp = SCI::RDR();  // ダミーリード

if(SCI::SISR.IICACKR() != 0) {
    utils::format("I2C Recv: Ack fail...\n");
    i2c_stop_();
    return false;
}

空読みするコードを追加したら正しく受信動作した、ヤレヤレ。

I2Cのドライバーは、全体的に考え直す必要のあるデバイスだ(SPIもそうだ
が・・)特に120MHzで動作しているプロセッサの場合は、ポーリングでデー
タを取得するような低速ドライバーは問題外と思う。(もったいない)
ただ、デバイスによって、通信の手順が色々なので、単純に割り込みにしただけで
は駄目で、柔軟性のある機能を提供する必要がある。
また、手順が面倒で、新規のI2Cデバイスドライバーを実装する手間が大変だと、
それはそれで、痛いので、よくよく考えて、全体を設計しておく必要がある。

とりあえず、受信と送信データはバッファリングして、それぞれの終了で設定した
コールバックを呼び出すようにすれば良さそうだ。

割り込み対応のI2Cドライバーは、次の機会に紹介する。

ソフトI2Cドライバー
SCI/I2C ドライバー
タッチスクリーンテストサンプル

-----
C++ に関するコラム:
C++ で C のキャストを使う人が多い・・・
以前に理由を聞いたら「冗長」だからーとか言ってた人がいるが、機能が違うので、
そんな理由で、使っては駄目だ。
前提として、キャストは、なるべく使わないように設計する必要がある。
「const」を取り除くような方法もいけない。
C 言語ベースの人はこの辺りを全く気にしない人が多いので困る。