RX72N Envision Kit を使った音楽プレイヤー

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/c648fdaa-df19-34bf-d282-0ba0d84304a2.jpeg" width="50%">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/04113a72-1ca6-d33c-e944-f39d1095e64b.jpeg" width="50%">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/d1ab7232-0e98-7fb4-74de-57e172d516e0.jpeg" width="50%">


## はじめに

- RX72N Envision Kit はまだ流通量が少ないようで入手が難しいようですが、ＣＰは高いので、何かガジェットを作るのなら、是非入手した方が良いと思います。
- 今回は、RX65N/RX72N Envision Kit、RX64M(DIY) ボードなどで動作可能なオーディオプレイヤーの紹介です。
- 内部的な動作や要点を簡単に説明してあります、「音」と「画像」を扱う場合の参考に出来ると思います。

以前、RX65N Envision Kit で作っていた、オーディオプレイヤーを、RX72N Envision Kit でも動作するようにしました。
※GUI は無いですが、RX64M でも動作します。

また、操作方法を見直して、GUI での一般的な操作で出来るようにしました。
※ファイラーは、多少独特ですが、タッチ操作で完結します。

全体的にかなり色々修正、マルチプラットホームで共通化できるように色々な面で改修を行いました。

以前は、オーディオインターフェースとして内蔵 D/A を利用していましたが、SSIE を使った I2S 出力をサポートしました。
※RX72N Envision Kit では、I2S からアナログに変換するデバイスが標準搭載されています。

FreeRTOS で、オーディオファイルのデコードと、GUI 操作を別タスクで動かすようにしました。

## ソースコードなど一式

[github AUDIO_sample project](https://github.com/hirakuni45/RX/tree/master/AUDIO_sample "AUDIO_sample")

ソースコードは Github にプッシュしてあります。

※コンパイルするには、RX フレームワーク全体が必要なので、RX プロジェクトをクローンする必要があります。
※MSYS2 による、RX マイコン用 gcc をビルドする必要があります。
※Renesas の統合環境（CC-RX、GNU-RX）では、コンパイルする事は出来ません。

## 全体の構成

全体は、以下のモジュールで構築されています。（-O3 の最適化で、バイナリー、900キロバイトくらいあります。）
- オーディオプレイヤー本体（main.cpp、audio_gui.hpp）
- FatFS (SD カードのファイルアクセス)
- libmad (MP3 のデコードを行う)
- libpng (PNG 画像のデコードを行う)
- zlib (libpng が利用する)
- picojpeg (JPEG 画像のデコードを行う)

## ハードウェアーの構成

- RX65N Envision Kit、RX64M ではチップ内蔵の12ビットD/Aからオーディオ出力します。
- RX72N Envision Kit では、内蔵オーディオから出力します。
- RX64M では、D/A 出力、SD カードハードウェアー、シリアル入出力などを接続する必要があります。

※RX65N Envision Kit では、D/A 出力を出して、アンプを入れたり、SD カードソケットを取り付ける改造が必要となります。
※RX72N Envision Kit では、改造は一切必要なく、SD カードにオーディオファイルを用意するだけです。
※RX64M は、DIY ボード向けのものになっています。（GR-KAEDE で動かすには、色々なポートの設定などを変更する必要があります。）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/0503db87-e508-f55a-eecf-9f9e5e3de88e.jpeg" width="50%">
※RX65N Envision Kit で D/A 出力などの配線。

## 各ハードウェアー基本設定 (main.cpp)

RX64M DIY：
- 水晶発振子 12MHz
- インジケーター LED 、PORT0、B7
- コンソール接続 SCI は SCI1
- SD カード MISO、PORTC、B3
- SD カード MOSI、PORT7、B6
- SD カード SPCK、PORT7、B7
- SD カード選択、PORTC、B2
- SD カード電源制御、PORT8、B2、アクティブ Low
- SD カード検出、PORT8、B1
- SD カードの書き込み禁止は使わない
- SDHI インターフェースによる SD カード制御（候補３のポートマップ）
- D/A 出力用波形バッファのサイズ指定（8192、1024）
※RX64M DIY ボードでは、SD カードのインターフェースとして、ソフト SPI を使っている。
   
```
#if defined(SIG_RX64M)
	typedef device::system_io<12'000'000> SYSTEM_IO;
	typedef device::PORT<device::PORT0, device::bitpos::B7> LED;
	typedef device::SCI1 SCI_CH;
	static const char* system_str_ = { "RX64M" };

	// SDCARD 制御リソース
	typedef device::PORT<device::PORTC, device::bitpos::B3> MISO;
	typedef device::PORT<device::PORT7, device::bitpos::B6> MOSI;
	typedef device::PORT<device::PORT7, device::bitpos::B7> SPCK;
	typedef device::spi_io2<MISO, MOSI, SPCK> SDC_SPI;  ///< Soft SPI 定義
	SDC_SPI	sdc_spi_;
	typedef device::PORT<device::PORTC, device::bitpos::B2> SDC_SELECT;	///< カード選択信号
	typedef device::PORT<device::PORT8, device::bitpos::B2, 0> SDC_POWER;	///< カード電源制御
	typedef device::PORT<device::PORT8, device::bitpos::B1> SDC_DETECT;	///< カード検出
	typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止
	typedef fatfs::mmc_io<SDC_SPI, SDC_SELECT, SDC_POWER, SDC_DETECT, SDC_WPRT> SDC;
	SDC		sdc_(sdc_spi_, 25'000'000);

	// マスターバッファはでサービスできる時間間隔を考えて余裕のあるサイズとする（8192）
	// DMAC でループ転送できる最大数の２倍（1024）
	typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
        static const int16_t ZERO_LEVEL = 0x8000;

	#define USE_DAC
	
```
---
RX65N Envision Kit：
- 水晶発振子 12MHz
- インジケーター LED 、PORT7、B0
- コンソール接続 SCI、SCI9
- SD カードの電源制御、PORT6、B4、アクティブ Low
- SD カードの書き込み禁止は使わない
- SDHI インターフェースによる SD カード制御（候補３のポートマップ）
- D/A 出力用波形バッファのサイズ指定（8192、1024）

```
#elif defined(SIG_RX65N)
	/// RX65N Envision Kit
	typedef device::system_io<12'000'000> SYSTEM_IO;
	typedef device::PORT<device::PORT7, device::bitpos::B0> LED;
	typedef device::SCI9 SCI_CH;
	static const char* system_str_ = { "RX65N" };

    typedef device::PORT<device::PORT6, device::bitpos::B4, 0> SDC_POWER;	///< '0'でＯＮ
    typedef device::NULL_PORT SDC_WP;		///< 書き込み禁止は使わない
    // RX65N Envision Kit の SDHI ポートは、候補３で指定できる
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WP, device::port_map::option::THIRD> SDC;
    SDC			sdc_;

	// マスターバッファはでサービスできる時間間隔を考えて余裕のあるサイズとする（8192）
	// DMAC でループ転送できる最大数の２倍（1024）
	typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
        static const int16_t ZERO_LEVEL = 0x8000;

	#define USE_DAC
	#define USE_GLCDC
```

---

RX72N Envision Kit：
- 水晶発振子 16MHz
- インジケーター LED 、PORT4、B0
- コンソール接続 SCI、SCI2（内蔵 USB シリアルインターフェース）
- SD カードの電源制御、PORT4、B2、アクティブ High
- SD カードの書き込み禁止は使わない
- SDHI インターフェースによる SD カード制御（候補３のポートマップ）
- SSIE 出力用波形バッファのサイズ指定（8192、1024）

```
#elif defined(SIG_RX72N)
	/// RX72N Envision Kit
	typedef device::system_io<16'000'000> SYSTEM_IO;
	typedef device::PORT<device::PORT4, device::bitpos::B0> LED;
	typedef device::PORT<device::PORT0, device::bitpos::B7> SW2;
	typedef device::SCI2 SCI_CH;
	static const char* system_str_ = { "RX72N" };

    typedef device::PORT<device::PORT4, device::bitpos::B2> SDC_POWER;	///< '1'でＯＮ
    typedef device::NULL_PORT SDC_WP;  ///< カード書き込み禁止ポート設定
    // RX72N Envision Kit の SDHI ポートは、候補３で指定できる
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WP, device::port_map::option::THIRD> SDC;
    SDC			sdc_;

	// マスターバッファはサービスできる時間間隔を考えて余裕のあるサイズとする（8192）
	// SSIE の FIFO サイズの２倍以上（1024）
	typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
        static const int16_t ZERO_LEVEL = 0x0000;

	#define USE_SSIE
	#define USE_GLCDC
```

---

## 描画ハードウェアー設定（RX65N/RX72N Envision Kit）audio_gui.hpp

- LCD_DISP、LCD の選択
- LCD_LIGHT、LCD バックライト
- LCD_ORG、描画ハードウェアー、GLCDC 開始アドレス
- FT5206_RESET、タッチパネルインターフェース、リセット信号
- FT5206_I2C、タッチパネルインターフェース、SCI(I2C) ポート

```
#if defined(SIG_RX65N)
		typedef device::PORT<device::PORT6, device::bitpos::B3> LCD_DISP;
		typedef device::PORT<device::PORT6, device::bitpos::B6> LCD_LIGHT;
		static const uint32_t LCD_ORG = 0x0000'0100;
		typedef device::PORT<device::PORT0, device::bitpos::B7> FT5206_RESET;
		typedef device::sci_i2c_io<device::SCI6, RB64, SB64, device::port_map::option::FIRST_I2C> FT5206_I2C;
#elif defined(SIG_RX72N)
		typedef device::PORT<device::PORTB, device::bitpos::B3> LCD_DISP;
		typedef device::PORT<device::PORT6, device::bitpos::B7> LCD_LIGHT;
		static const uint32_t LCD_ORG = 0x0080'0000;
		typedef device::PORT<device::PORT6, device::bitpos::B6> FT5206_RESET;
		typedef device::sci_i2c_io<device::SCI6, RB64, SB64, device::port_map::option::THIRD_I2C> FT5206_I2C;
#endif
```

---

## メイン部

今回 FreeRTOS を利用して、オーディオコーデックのデコード部と、GUI 操作部を分け、スレッドで平行動作させています。
FreeRTOS ベースなので、起動したら、二つのタスクを生成後、それらを起動します。

```
int main(int argc, char** argv)
{
	SYSTEM_IO::setup_system_clock();

	{  // SCI 設定
		static const uint8_t sci_level = 2;
		sci_.start(115200, sci_level);
	}

	{  // SD カード・クラスの初期化
		sdc_.start();
	}

	utils::format("\r%s Start for Audio Sample\n") % system_str_;

    {
        uint32_t stack_size = 4096;
        void* param = nullptr;
        uint32_t prio = 2;
        xTaskCreate(codec_task_, "Codec", stack_size, param, prio, nullptr);
    }

    {
        uint32_t stack_size = 8192;
        void* param = nullptr;
        uint32_t prio = 1;
        xTaskCreate(main_task_, "Main", stack_size, param, prio, nullptr);
    }

	vTaskStartScheduler();
}
```

---

## オーディオ・コーデック・タスク

- name_t クラスを使って、GUI タスクから、再生ファイル名を受け取っています。
- 受け取った名前は、コーデックマネージャーに渡して、オーディオ再生しています。

```
	void codec_task_(void *pvParameters)
	{
		// オーディオの開始
		start_audio_();

		while(1) {
			if(name_t_.get_ != name_t_.put_) {
				if(strlen(name_t_.filename_) == 0) {
					codec_mgr_.play("");
				} else {
					if(std::strcmp(name_t_.filename_, "*") == 0) {
						codec_mgr_.play("");
					} else {
						codec_mgr_.play(name_t_.filename_);
					}
				}
				++name_t_.get_;
			}
			codec_mgr_.service();

			vTaskDelay(10 / portTICK_PERIOD_MS);
		}
	}
```

---

## 操作 (GUI) タスク

- GUI (GLCDC) を使う場合と、コンソールのみの場合を分けています。
- GUI では、ファイル名が選択されたら、それを、コーデックのタスクに転送しています。
- GUI では、オーディオファイル再生時、再生経過時間を受け取って、表示に反映しています。
- シリアル入出力のコマンドライン操作をサポートしています。
- SD カードの操作系をサービスしています。（SD カードの抜き差しによるマウントなど）

```
	void main_task_(void *pvParameters)
	{
		cmd_.set_prompt("# ");

		LED::DIR = 1;
#ifdef USE_GLCDC
		gui_.start();
		gui_.setup_touch_panel();
		gui_.open();  // 標準 GUI
		volatile uint32_t audio_t = audio_t_;
#endif
		while(1) {
#ifdef USE_GLCDC
			if(gui_.update(sdc_.get_mount(), codec_mgr_.get_state())) {
				// オーディオ・タスクに、ファイル名を送る。
				strncpy(name_t_.filename_, gui_.get_filename(), sizeof(name_t_.filename_));
				name_t_.put_++;
			}
			if(audio_t != audio_t_) {
				gui_.render_time(audio_t_);
				audio_t = audio_t_;
			}
                        cmd_service_();
#else
            // GLCDC を使わない場合（コンソールのみ）
            auto n = cmt_.get_counter();
            while((n + 10) <= cmt_.get_counter()) {
                 vTaskDelay(1 / portTICK_PERIOD_MS);
            }
            if(codec_mgr_.get_state() != sound::af_play::STATE::PLAY) {
                 cmd_service_();
            }
#endif
			sdc_.service();
			update_led_();
		}
	}
```

---

## FreeRTOS 対応のシリアル入出力

- FreeRTOS では、共有するシリアルの入出力を排他制御する必要があります。
- あまり効率は良くないですが、簡易的にその対応をしています。
- 非常に簡単な方法で、ロック用オブジェクトを作成して、それをロックしてからアクセスし、終わったらロックを外します。
- 「volatile」を付ける事で、最適化されても、オブジェクトの操作が無効にならないようにしています。

```
	void sci_putch(char ch)
	{
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        sci_.putch(ch);
        lock_ = false;
	}

	void sci_puts(const char* str)
	{
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        sci_.puts(str);
        lock_ = false;
	}

	char sci_getch(void)
	{
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        auto ch = sci_.getch();
        lock_ = false;
        return ch;
	}
```

---

## 同期オブジェクトを使った通信

- オーディオコーデック側は、「状態」を生成しています。
- GUI 側は、その状態を見て、操作を切り替えています。
- GUI 側は、本来、内蔵ハードウェアー（DRW2D）エンジンを使う事が望まれますが、簡潔に済ます為、ソフトウェアーでフレームバッファに直接描画しています。
- また、GUI 側から、コーデック側を制御するオブジェクトを定義してあります。

```
		struct th_sync_t {
			volatile uint8_t	put;
			volatile uint8_t	get;
			th_sync_t() : put(0), get(0) { }
			void send() { ++put; }
			bool sync() const { return put == get; }
			void recv() { get = put; }
		};
```

※単方向で良いので、簡易な方法を使っています、これなら、オブジェクトを同時にアクセスする事が無いので、競合が発生しません。

送る側：（FF ボタンが押された場合）

```
			ff_.at_select_func() = [this](uint32_t id) {
				play_ff_.send();
			};
```

受け取る側：（コーデックの制御を行う）

```
			if(!play_ff_.sync()) {
				play_ff_.recv();
				return sound::af_play::CTRL::NEXT;
			}
```

---

## 最後に

かなり、ツギハギ感がありますが、とりあえず、何とかなっています。

実質的なソースコードは、main.cpp と audio_gui.hpp しかありません、ですが、フレームワークが提供するクラスを色々使って実装しています。

C++ テンプレートや、C++ クラスの場合、ある程度の汎用性をかなり自由に実現できる為と思います。

複数のプラットホームで共有できるのも、テンプレートクラスに依る部分が大きいと思えます。

多くは新規に実装した物もありますが、他のオープンソースを多く利用して実現しています、良い時代です～

---

## ライセンス

Audio Player: (MIT open source license)

FreeRTOS: (MIT open source license)
FatFs: BSD like
libmad: See libma/libmad/COPYRIGHT (G.P.L. v2)
libpng: See libpng/libpng/LICENSE  (libpng license)
zlib: (zlib License)
picojpeg: Public domain, Rich Geldreich <richgel99@gmail.com>