ArduinoとRDA5807でFMラジオを聴く

![cover](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/c37d3b38-8906-c935-ac6c-2b4795279e78.jpeg)

FMラジオIC **RDA5807FP** または **RDA5807H** と Arduino を使って FM ラジオが聴ける回路を作ります。
この記事では Arduino Nano を使っていますが、プログラムは Arduino UNO, Arduino Nano Every などにも対応しています。

Arduino 自体の使い方は他の記事をご参照ください。

# 用意するもの

## RDA5807H の場合

秋月電子で販売されている RDA5807**H** を使ったモジュールの場合、2.54mm のピンピッチに変換する必要があるため少し加工が必要です。水晶振動子は内臓されているため別途用意する必要はありません。

|部品名|はんだ付け|入手先 (参考)|
|---|---|---|
|**RDA5807H**<br />FMレシーバIC| 必要 | [秋月電子](https://akizukidenshi.com/catalog/g/g117245/) |
|**ピンヘッダ**<br />1x5 2個| 必要 | [秋月電子](https://akizukidenshi.com/catalog/g/g104398/) など  

## RDA5807FP の場合

RDA5807**FP** の場合は SOP16 から DIP に変換する必要があります。また、水晶振動子は付属していないので自前で用意する必要があります。

|部品名|画像|はんだ付け|入手先 (参考)|
|---|:--:|---|---|
|**RDA5807FP**<br />FMレシーバIC|<img width="240" alt="RDA5807FP" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/6b0ea00a-d522-06f7-ca72-b1b381634057.jpeg">| 必要 | [aitendo](https://www.aitendo.com/product/4797) |
|**水晶振動子**<br />32.768kΩ 1個|<img width="240" alt="水晶振動子 32.768kΩ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/5d28301a-7fe7-a72d-4f3f-1a1aa742a1ef.jpeg">| 必要なし | 基準周波数用 | [秋月電子](https://akizukidenshi.com/catalog/g/g104005/) など
|**SOP-16 DIP化基板**|<img width="240" alt="SOP-16 DIP化基板" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/e89bf8f5-5005-5a35-955b-7060224988ae.jpeg">| 必要 | [秋月電子](https://akizukidenshi.com/catalog/g/g106541/) など


## その他 共通で必要な部品

|部品名|画像|参考|
|---|:--:|---|
|**Arduino Nano**<br/>(Arduino UNO でも可)|<img width="240" alt="Arduino Nano" src="https://qiita-image-store.s3.amazonaws.com/0/30370/af28fa91-7ae2-1f5c-fada-d6f532bceee3.jpeg">|[秋月電子](https://akizukidenshi.com/catalog/g/g109059/) など |
|**AE-PCA9306**<br/>I<sup>2</sup>Cバス用双方向<br/>電圧レベル変換モジュール|<img width="240" alt="AE-PCA9306" src="https://qiita-image-store.s3.amazonaws.com/0/30370/bfa1e1d3-eddf-803b-7af8-18d0ee8078dd.jpeg">|[秋月電子](https://akizukidenshi.com/catalog/g/g105452)
|**3.5mmステレオミニジャック DIP化基板**|<img width="240" alt="3.5mmステレオミニジャック DIP化基板" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/041489c4-c9ac-f064-7311-e7c428e89e22.jpeg"> | [秋月電子](https://akizukidenshi.com/catalog/g/g105363/) など 
|**炭素皮膜抵抗**<br />10kΩ 2個|<img width="240" alt="炭素皮膜抵抗 10kΩ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/55978e44-8432-c242-7311-b813bf4df3c7.jpeg">| DC除去用<br/>金属皮膜抵抗でも可 |
|**電解コンデンサ**<br />4.7μF 2個|<img width="240" alt="電解コンデンサ 4.7μF" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/2857b434-3947-06db-1b85-476baea4b48f.jpeg">| DC除去用<br/>積層セラミックコンデンサでも可 |
|**積層セラミックコンデンサ**<br />100pF 1個|<img width="240" alt="積層セラミックコンデンサ 100pF" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/7b264cbb-2ea4-8539-cec4-d36f5ea63dc7.jpeg">| FMアンテナ用 |
|**積層セラミックコンデンサ**<br />0.1μF 1個|<img width="240" alt="積層セラミックコンデンサ 0.1μF" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/899787df-f21e-497e-fa9a-76bdf80b0d1d.jpeg">| バイパスコンデンサ用 |

抵抗や静電容量は多少の値の違いがあっても構いません。

## アンテナの感度を上げる場合

|部品名|画像|参考|
|---|:---:|---|
|**144MHz帯ハムバンドアンテナ**| (準備中) | [秋月電子](https://akizukidenshi.com/catalog/g/g100064/)
|**アンテナ変換ケーブル**<br />3.5mm ミニプラグ-BNCメス| (準備中) | [秋月電子](https://akizukidenshi.com/catalog/g/g116598/)
|**3.5mmステレオミニジャック DIP化基板**|<img width="240" alt="3.5mmステレオミニジャック DIP化基板" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/041489c4-c9ac-f064-7311-e7c428e89e22.jpeg">| [秋月電子](https://akizukidenshi.com/catalog/g/g105363/) など 

記事中ではブレッドボードの配線を使ってアンテナを作っていますが、さらなる感度向上を行う際は上記のアンテナが使えます。ステレオミニジャックの L, R を短絡させ、G ピンは 10kΩ 程度の抵抗をつけて接続します。

# RDA5807

RDA5807 は I<sup>2</sup>C で制御する DSP ラジオ IC です。RDA5807<b>FP</b> のパッケージはブレッドボードには直接挿入できませんが、基準周波数となる水晶振動子（クリスタル）を外部の発振回路を使わずに直接 IC に接続して使え、少ない部品数で構成できます。

さらに水晶振動子を内臓する形となったFMラジオモジュールも販売されており、こちらは RDA5807<b>H</b> が使われています。他にも RDA5807<b>M</b> という別パッケージも存在しますが、形状とピン配置が異なるだけで内部のレジスタに違いはありません。

動作電圧は 2.7～3.3V (typ. 3.0V) となっており、5V のままでは使えません。Arduino Nano などのロジックレベルが 5V のマイコンで使用するには電圧レベルの変換が必要です。今回は Arduino Nano に内蔵されている 3.3V レギュレータを使って電源電圧を供給し、PCA9306 を使って I<sup>2</sup>C の信号を 5V ←→ 3.3V へ相互変換します。PCA9306 には 1kΩ のプルアップ抵抗が組み込まれているため、今回はこれをそのまま使います。

RDA5807 は [RDS/RBDS](https://ja.wikipedia.org/wiki/Radio_Data_System)、I<sup>2</sup>S にも対応していますが、日本では RDS はサービス外のためこの記事では解説しません。

# 回路と配線
| |**回路図**|**実装例**|**配線例**|
|:---|:--:|:--:|:--:|
|RDA5807H の場合|![回路図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/e5e5cb16-879b-4c1c-a506-5629c8bd0da6.png)|(準備中)|(準備中)|
|RDA5807FP の場合|![回路図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/61d15cb2-303b-8fff-4bef-e91f225ec368.png)|![実装例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/c6fa4c03-64a6-6c33-ce19-4063fa26fefb.jpeg)|![配線例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30370/31a6efe6-469c-612f-8f10-43a132417fe1.jpeg)|

Arduino Nano からの 5V ピンは PCA9306 の REF1 ピンに接続し、誤って RDA5807FP に接続しないよう注意してください。REF2 ピンと RDA5807 に接続するのは Arduino Nano から供給される 3.3V です。

水晶振動子は RDA5807 から物理的・電気的に近い場所に配置してください。

アンテナはオプションです。電波強度の高い場所であれば屋内であっても 100pF のコンデンサなしに受信できる場合があります。今回はブレッドボード上の電源ラインも使って経路長が 60cm ほどのアンテナにしました。

# プログラム

Arduino 用の [**RDA5807M**](https://github.com/csdexter/RDA5807M) というライブラリを使います。付属するスケッチ [RDA5807M_Example.ino](https://github.com/csdexter/RDA5807M/blob/43139c988ddb4c600801f38f313dab778118e4e2/RDA5807M_Example/RDA5807M_Example.ino) の一部を手直しして使います。

サンプルでは受信バンドが `RDA5807M_BAND_WEST` （欧州/アメリカ, 87-108 MHz） になっていますが、[FM補完中継局](https://ja.wikipedia.org/wiki/FM%E8%A3%9C%E5%AE%8C%E4%B8%AD%E7%B6%99%E5%B1%80) の受信も行うため `RDA5807M_BAND_WORLD` (76-108 MHz) に変更します[^band]。

[^band]: `RDA5807M_BAND_JAPAN` も存在しますが、これは2011年7月以前の周波数バンド 76-91 MHz となっており、91.1 MHz 以上の選局ができません。FM補完中継局の周波数は 90.1-94.9 MHz です。

```cpp:RDA5807M_Example.ino（抜粋）
#include <Wire.h>
#include <RDA5807M.h>

RDA5807M radio;
char command;
word status, frequency;

void setup()
{
  Serial.begin(9600);
  radio.begin(RDA5807M_BAND_WORLD);  // <---- バンドを WORLD に変更
}

void loop()
{
  if(Serial.available()){
    command = Serial.read();
    switch(command){
...
```

プログラムを書き込んだらイヤホンを接続し、Arduino Nano とシリアル通信でコマンドを送ります。

```plaintext:コマンド一覧（日本語訳）
利用可能なコマンド一覧:
* v/V     - ボリュームを 下げる/上げる
* s/S     - 受信周波数の シークダウン/シークアップ
* m/M     - オーディオ出力の ミュート/ミュート解除
* f       - 受信中の周波数を表示
* q       - 受信中の放送局の RSSI 値を表示
* t       - デコーダ状態のレジスタ値を表示
* ?       - このコマンド一覧を表示
```

RSSI とは [Received signal strength indication](https://en.wikipedia.org/wiki/Received_signal_strength_indication) のことで、受信電波強度のことです。単位は dBμV で、高いほど電波強度が強いことを表します。範囲は 0 ～ 127 dBμV です[^rssi]。

[^rssi]: RDA5807 のデータシートには以下のように記載がありますが、`15:9` `RSSI[6:0]` と記述されているように 7 ビット分がレジスタに割当がありますので最大値は $2^7-1 = 127$ (dBμV) です。誤植と思われます。実際に東京都内では 64 dBμV 以上の数値がしばしば取得できます。（以下仕様書を引用）<blockquote>RSSI.<br/>&nbsp;&nbsp;000000 = min<br/>&nbsp;&nbsp;111111 = max<br/>RSSI scale is logarithmic.</blockquote>

何も鳴らないときは I<sup>2</sup>C の接続が正しいか確認してください。シークしても選局ができない場合はアンテナを接続するか、水晶振動子の接続を調整してください。

なお、32.768kHz の水晶振動子は発振までに時間がかかり、完全に放電した状態で電源を入れた直後は選局ができないことがあります。この場合、電源投入 5 秒ほどしてから再度 `radio.begin()` で初期化をやり直すと選局できます。

# 応用

受信するだけであればここまでですが、ここからは RDA5807 の詳しい制御について解説していきます。

## I<sup>2</sup>C アドレスとレジスタ

RDA5807 はレジスタへのアクセス方法に応じて 2 種類の I<sup>2</sup>C アドレスを持つ変わったデバイスです。

- `0x10` : シーケンシャル (アドレス `0x02` から開始)
- `0x11` : ランダム (アドレスを逐一指定)

このように 2つの I<sup>2</sup>C アドレスを持つことで、たとえば `0x10` が別デバイスで使われている場合は `0x11` のランダムアクセスのみを選択することになります。レジスタのデータ幅は 2バイト = 16ビット となっています。

### シーケンシャルのとき

書き込み時は上位バイト、下位バイトの順で送信し、必要な分だけを送ります。開始アドレスは必ず `0x02` になります。読み込み時はシンプルに、必要なレジスタ数 `count` をリクエストして読み出すだけです。

```cpp:RDA5807M.cpp
void RDA5807M::setRegisterBulk(byte count, const word regs[]) {
    Wire.beginTransmission(RDA5807M_I2C_ADDR_SEQRDA);

    for(byte i=0; i < count; i++) {
        Wire.write(highByte(regs[i]));
        Wire.write(lowByte(regs[i]));
    };

    Wire.endTransmission(true);
};

void RDA5807M::getRegisterBulk(byte count, word regs[]) {
    Wire.requestFrom(RDA5807M_I2C_ADDR_SEQRDA, count * 2, true);

    for(byte i=0; i < count; i++) {
        //Don't let gcc play games on us, enforce order of execution.
        regs[count] = (word)Wire.read() << 8;
        regs[count] |= Wire.read();
    };
};

```

### ランダムのとき

シーケンシャルとは異なり、任意のレジスタだけを読み出せます。

書き込み時はアドレス、上位バイト、下位バイトの順で送信します。読み込み時はアドレスを送った後、2バイト分を読み込みます。

```cpp:RDA5807M.cpp
void RDA5807M::setRegister(byte reg, const word value) {
    Wire.beginTransmission(RDA5807M_I2C_ADDR_RANDOM);
    Wire.write(reg);
    Wire.write(highByte(value));
    Wire.write(lowByte(value));
    Wire.endTransmission(true);
};

word RDA5807M::getRegister(byte reg) {
    word result;

    Wire.beginTransmission(RDA5807M_I2C_ADDR_RANDOM);
    Wire.write(reg);
    Wire.endTransmission(false);
    Wire.requestFrom(RDA5807M_I2C_ADDR_RANDOM, 2, true);
    //Don't let gcc play games on us, enforce order of execution.
    result = (word)Wire.read() << 8;
    result |= Wire.read();

    return result;
};
```

## 受信周波数を直接指定する

使用したライブラリの example ではシークで周波数の設定（変更）を行っていますが、直接に周波数を設定するメソッド `setFrequency` がライブラリに用意されています。

対になる `getFrequency` とともに、周波数は 16ビット符号なし整数 (word = uint16_t) で指定します。求める周波数を 100 倍した数値で、たとえば 81.50 MHz の場合は 8150 となります。

```cpp
void setup() {
  radio.begin(RDA5807M_BAND_JAPAN);
  radio.setFrequency(8150);  // <---- 81.50 MHz
}
```

受信バンドとチャンネル間隔とレジスタを共有しているうえ、それらの設定によって周波数の計算値も変わります。このため、該当のレジスタ内容をまず読み、その結果から周波数の計算をして書き込み直します。

具体的には $f$ を受信周波数 (MHz)、$S$ をチャネル間隔 (MHz)、$C$ をレジスタ値 `CHAN`、$B$ を受信バンド (MHz) とすると、

```math
\begin{align}
f &= SC + B \\
\therefore C &= \frac{f - B}{S}
\end{align}
```

という関係になっており $f = 81.5$ (MHz)、$S = 0.1$ (MHz)、$B = 76.0$ (MHz) とすると、レジスタ `CHAN` に設定されるべき値 $C$ は、

```math
\begin{align}
C &= \frac{81.5 - 76.0}{0.1}\\
  &= 55
\end{align}
```

となります。メソッド `setFrequency` 内で上記の計算が行われています。

## ボリュームの直接指定

ライブラリおよびサンプルではボリュームの増加と減少のみですが、直接の指定もできます。範囲は 0（無音） ～ 15（音量大） です。

```cpp
byte getVolume(RDA5807M *radio) {
  return radio->getRegister(RDA5807M_REG_VOLUME) & RDA5807M_VOLUME_MASK;
}

bool setVolume(RDA5807M *radio, byte volume) {
  if (volume > RDA5807M_VOLUME_MASK)
    return false;
  else {
    updateRegister(RDA5807M_REG_VOLUME, RDA5807M_VOLUME_MASK, volume);
    return true;
  }
}
```

ところでメソッド `RDA5807M::volumeDown` の実装を見ると以下の記述があります。ライブラリではボリュームがゼロのときにミュートも行われるようになっています。
実際は RDA5807 の内部でミュートとオーディオ出力の Hi-Z 化（つまり無接続化）も行われているので、わざわざ `mute()` を呼び出す必要はないということです。

```cpp:RDA5807M.cpp
if(!(volume - 1) && alsoMute)
    //If we are to trust the datasheet, this is superfluous as a volume
    //of zero triggers mute & HiZ on its own.
    mute();
```

## 強制モノラル化

自動ステレオと強制モノラルのフラグが存在し、デフォルトは `0` (自動ステレオ)です。電波強度が十分かつステレオに対応している場合のみステレオ受信となります。スピーカーが 1 つのみの場合は有効 `1` にしてモノラル化するとよいでしょう。

```cpp
void setMonoSelect(RDA5807M *radio, bool mono) {
  radio->updateRegister(RDA5807M_REG_CONFIG, RDA5807M_FLG_MONO, mono ? 0 : RDA5807M_FLG_MONO);
}
```

## 低音ブースト

低音を強調するフラグが存在し、デフォルトは `0` (無効)です。

```cpp
void setBassBoost(RDA5807M *radio, bool boost) {
  radio->updateRegister(RDA5807M_REG_CONFIG, RDA5807M_FLG_BASS, boost? RDA5807M_FLG_BASS : 0);
}
```

## ソフトリセット

ソフトリセットを担うフラグも存在します。デフォルトは `0` (リセットなし)です。一度 `1` にすると、自動的に `0` に復帰しますが、自前で RDA5807 のレジスタを初期化し直す必要があります。

```cpp
void setSoftReset(RDA5807M *radio) {
  radio->updateRegister(RDA5807M_REG_CONFIG, RDA5807M_FLG_RESET, RDA5807M_FLG_RESET);
}
```

## ステータスフラグ

シークによる選局の成功、現在の周波数での受信、ステレオ受信の可否をそれぞれ取得できます。ここでは代表的なステータスのみを挙げます。

```cpp
// true: シークの失敗, false: 成功
bool isFailedSeek(RDA5807M *radio) {
  return radio->getRegister(RDA5807M_REG_STATUS) & RDA5807M_STATUS_SF;
}

// true: 受信成功, false: 失敗
bool isStation(RDA5807M *radio) {
  return radio->getRegister(RDA5807M_REG_RSSI) & RDA5807M_FLG_FMTRUE;
}

// true: ステレオ受信中, false: モノラル受信中
bool isStereo(RDA5807M *radio)
{
  return radio->getRegister(RDA5807M_REG_STATUS) & RDA5807M_STATUS_ST;
}
```

# 参考文献

- **RDA5807FP datasheet v1.2**
- **RDA5807MをArduinoで制御　DSP　ラジオモジュール**<br/>https://qiita.com/dabodabo/items/f8b52413849291a2d462