ベランダに寂しくFMアンテナが立っています。
最近は、radikoを使っていたので、事務所にあったアナログFMチューナは売却してしまいアンテナだけ寂しくベランダに立っていました。
なにやら寂しく、災害時にPC(ネット)が使えなくなることを考えるとワイドFM対応の電池で動作するチューナもあった方が良いかと思い、ワイドFMチューナーを調べてみましたが、それなりの性能のものしかないようで(異常に高価なチューナーを除き)それならと、自分で作ってみることにしました。
RDA5807M S INGLE -C HIP B ROADCAST FM R ADIO T UNERの性能がなかなか良い
RDA5807Mを使用した安価な中国製のDSP FMチューナモジュールが、仕様上の性能はなかなか良いようです。
S/N比が55dB、Stereo Channel Separationが35dB(min)、THDが0.15%
このスペックは、現在一般に販売されている2万円以上するFMチューナーとほとんど遜色ない値です。(最近の市販チューナーの性能が寂しいのがほんとうの処ですが。。。。)
ワイドFM DSPラジオ (RDA5807M)を作りました。
最低限の回路構成のFMラジオ 回路図
RFプリアンプやLINE OUT用バッファーアンプ、スピーカなど最初はいろいろ考えていましたが、手持ちの部品や気持ちの問題で、今回は最小限の回路構成となりました。
出来上がってみると、ちゃんとしたFMアンテナを接続することで十分な受信感度で、DMA5807Mの出力(ボリュームMAX)がほぼライン信号レベルでそのまま据え置きのオーディオアンプへも接続できました。
ちなみにこの回路図は、あとで作ったので、多少間違いがあるかもなので、参考程度(-_-;)
ワイドFM DSPラジオ (RDA5807M)のプログラム
//#include <Arduino.h>
#include <Wire.h>
//#include <radio.h>
#include <RDA5807M.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#define debug false
#define FIX_BAND RADIO_BAND_FMWORLD //RADIO_BAND_FM (76-108 MHz)
#define FIX_VOLUME 3 //Audio Volume Level
#define SW0 6
#define SW1 7
#define VOL_MAX 15
#define VOL_MIN 1
#define VOL_DIF 2
#define MEMORY0 100 //byte
#define MEMORY1 101 //byte
#define MEMORY2 103 //word
#define DIMMER 30 // max 12000 sec do't dimmer/12001-65535
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
//#define SCREEN_HEIGHT 64 // Free RAM > 1kB + a?
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RDA5807M radio;
RADIO_INFO ri;
const uint16_t STATION[] = { 7970, 8770, 9170 }; //Station Tuned = 79.70 MHz.
const char* STATION_NAME[] = { "FM EHIME", "NHK FM", "RNB FM" } ;
const uint8_t STATION_NUM = 3;
const uint8_t rssi_min = 45;
const uint8_t rssi_stereo = 50;
int8_t num = 1;
int8_t vol = FIX_VOLUME;
uint16_t freq = 0;
uint16_t lcount = 0;
uint8_t scan_freq = 0;
uint8_t rssi;
void dispLCD() {
char s[12];
if (lcount <= DIMMER) {
display.clearDisplay();
display.setCursor(60, 24);
display.setTextSize(1);
display.print("VOL:");
display.setCursor(92, 16);
display.setTextSize(2);
display.println(vol);
display.display();
delay(500); // RSSIを正常に読み込むため
display.setTextSize(2);
display.setCursor(0, 0);
freq = radio.getFrequency();
if (scan_freq > 0) {
radio.formatFrequency(s, sizeof(s));
display.println(s);
} else {
display.println(STATION_NAME[num]);
}
display.setTextSize(1);
radio.getRadioInfo(&ri);
display.setCursor(0, 16);
display.print("RSSI:");
rssi = ri.rssi;
display.print(rssi);
display.setCursor(0, 24);
if (rssi < rssi_stereo) {
radio.setMono(true);
display.print("mono_F");
} else {
if (ri.stereo) {
display.print("stereo");
} else {
display.print("mono_?");
}
}
display.display();
if (debug ) {
delay(200);
radio.formatFrequency(s, sizeof(s));
Serial.print("Station:");
Serial.println(s);
Serial.print("Radio:");
radio.debugRadioInfo();
Serial.print("Audio:");
radio.debugAudioInfo();
//radio.debugScan ();
radio.debugStatus ();
Serial.flush();
}
}
}
void setup() {
pinMode(SW0, INPUT_PULLUP);
pinMode(SW1, INPUT_PULLUP);
if (debug ) {
Serial.begin(9600);
delay(300);
Serial.println("Wide FM Radio");
}
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
// Clear the buffer
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("Wide FM");
display.println(" Radio");
display.display();
delay(2000);
radio.init();
num = EEPROM.read(MEMORY0);
if (num == 100 ) {
scan_freq = 1;
freq = EEPROM.read(MEMORY2) + 100 * EEPROM.read(MEMORY2 + 1);
} else {
if (num < 0 || num >= STATION_NUM ) { // EEPROM 不正の場合
num = 0;
}
freq = STATION[num];
}
vol = EEPROM.read(MEMORY1);
if (vol < VOL_MIN || vol > VOL_MAX) vol = FIX_VOLUME;
radio.setMute(true);
radio.setBandFrequency(FIX_BAND, freq);
radio.setVolume(vol);
radio.setMono(false);
radio.setMute(false);
radio.setBassBoost(false);
delay(200);
dispLCD();
lcount = 0;
}
void loop() {
int key0 = digitalRead(SW0);
int key1 = digitalRead(SW1);
int key = 10 * key1 + key0;
if ((key < 11) && (lcount > DIMMER)) {
display.dim(0);
dispLCD();
lcount = 0;
} else {
switch (key) {
case 10:
lcount = 0;
vol += VOL_DIF;
if (vol > VOL_MAX) vol = VOL_MIN;
radio.setVolume(vol);
dispLCD();
break;
case 1:
lcount = 0;
display.clearDisplay();
display.print("Key check");
display.display();
delay(1000);
if (digitalRead(SW1)) {
scan_freq = 0;
num += 1;
if (num >= STATION_NUM) num = 0;
freq = STATION[num];
radio.setMute(true);
radio.setFrequency(freq);
radio.setMute(false);
} else {
if (scan_freq != 1) radio.setFrequency(7600);
scan_freq = 1;
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
display.println("Scanning");
display.display();
do {
radio.seekUp(true);
delay(500); // RSSIを正常に読み込むため
radio.getRadioInfo(&ri);
rssi = ri.rssi;
} while (rssi < rssi_min );
}
dispLCD();
break;
case 0:
lcount = 0;
if (scan_freq == 1) {
num = 100;
EEPROM.write(MEMORY2 + 1, int(freq / 100));
EEPROM.write(MEMORY2, freq % 100);
}
EEPROM.write(MEMORY0, num);
EEPROM.write(MEMORY1, vol);
display.dim(1);
delay(500);
display.dim(0);
break;
}
}
delay(1000);
if (lcount < 12000)lcount += 1;
if (lcount > DIMMER) display.dim(1); //画面を消灯
}
RadioライブラリのRDA5807M.hを使用したのですが、どうもRSSIの値が可怪しいです。
ライブラリsrcのRDA5807M.cppの417行目あたりの
info->rssi = registers[RADIO_REG_RB] >> 9; //10 -> 9
10シフトしているのを9シフトに変更することで正しく(?)表示するようになりました。
Adafruit_SSD1306互換のI2C接続の128x64OLEDを使用したのですが、このプログラムでは128×64にするとメモリ不足で正常に動作しません。ライブラリをブラッシュアップすれば動作するかもですが。。。。
ちょっと古いライブラリーのようですが、PU2CLR RDA5807の方が空きメモリが多くなるようなので、こちらでプログラムを書き直してみました。
//#include <Arduino.h>
#include <Wire.h>
#include <RDA5807.h> //PU2CLR RDA5807 Arduino Library
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#define debug false
#define FIX_VOLUME 3 //Audio Volume Level
#define SW0 6
#define SW1 7
#define VOL_MAX 15
#define VOL_MIN 1
#define VOL_DIF 2
#define MEMORY0 100 //byte
#define MEMORY1 101 //byte
#define MEMORY2 103 //word
#define DIMMER 30 // max 12000 sec do't dimmer/12001-65535
#define RSSI_MIN 45
#define RSSI_STEREO 50
#define SCREEN_WIDTH 128 // OLED display width, in pixels
//#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define SCREEN_HEIGHT 64 // Free RAM > 1kB + a?
#define SCREEN_MAG 2
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RDA5807 radio;
const uint16_t STATION[] = { 7970, 8770, 9170 }; //Station Tuned = 79.70 MHz.
const char* STATION_NAME[] = { "FM EHIME", "NHK FM", "RNB FM" } ;
const uint8_t STATION_NUM = 3;
int8_t num = 1;
int8_t vol = FIX_VOLUME;
uint16_t freq = 0;
uint16_t lcount = 0;
uint8_t scan_freq = 0;
uint8_t rssi;
void showFrequency() {
freq = radio.getFrequency();
}
void dispLCD() {
char s[12];
if (lcount <= DIMMER) {
display.clearDisplay();
display.setCursor(60, 24 * SCREEN_MAG);
display.setTextSize(1, 1 * SCREEN_MAG);
display.print("VOL:");
display.setCursor(92, 16 * SCREEN_MAG);
display.setTextSize(2, 2 * SCREEN_MAG);
display.println(vol);
display.display();
display.setTextSize(2, 2 * SCREEN_MAG);
display.setCursor(0, 0);
if (scan_freq > 0) {
display.print(int(freq / 100));
display.print(".");
display.print(int(freq % 100));
display.println(" MHz");
} else {
display.println(STATION_NAME[num]);
}
display.setTextSize(1, 1 * SCREEN_MAG);
delay(500); // RSSIを正常に読み込むため
rssi = radio.getRssi();
display.setCursor(0, 16 * SCREEN_MAG);
display.print("RSSI:");
display.print(rssi);
display.setCursor(0, 24 * SCREEN_MAG);
if (rssi < RSSI_STEREO) {
radio.setMono(true);
display.print("mono_F");
} else {
if (radio.isStereo()) {
display.print("stereo");
} else {
display.print("mono_?");
}
}
display.display();
if (debug ) {
delay(200);
Serial.print("Station:");
Serial.print(radio.getFrequency());
Serial.print("kHz RSSI:");
Serial.println(radio.getRssi());
Serial.flush();
}
}
}
void setup() {
pinMode(SW0, INPUT_PULLUP);
pinMode(SW1, INPUT_PULLUP);
if (debug ) {
Serial.begin(9600);
delay(300);
Serial.println("Wide FM Radio");
}
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
// Clear the buffer
display.clearDisplay();
display.setTextSize(2, 2 * SCREEN_MAG);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println(" Wide FM");
display.println(" DSP Radio");
display.display();
delay(2500);
radio.setup(CLOCK_32K, OSCILLATOR_TYPE_CRYSTAL);
//radio.setRDS(false);
radio.setSpace(0);
radio.setFmDeemphasis(1); //0 = 75 μs; 1 = 50 μs
num = EEPROM.read(MEMORY0);
if (num == 100 ) {
scan_freq = 1;
freq = EEPROM.read(MEMORY2) + 100 * EEPROM.read(MEMORY2 + 1);
} else {
if (num < 0 || num >= STATION_NUM ) { // EEPROM 不正の場合
num = 0;
}
freq = STATION[num];
}
vol = EEPROM.read(MEMORY1);
if (vol < VOL_MIN || vol > VOL_MAX) vol = FIX_VOLUME;
radio.setMute(true);
radio.setBand(RDA_FM_BAND_WORLD);
radio.setFrequency(freq);
radio.waitAndFinishTune();
radio.setVolume(vol);
radio.setMono(false);
radio.setMute(false);
radio.setBass(false);
delay(200);
dispLCD();
lcount = 0;
}
void loop() {
int key0 = digitalRead(SW0);
int key1 = digitalRead(SW1);
int key = 10 * key1 + key0;
if ((key < 11) && (lcount > DIMMER)) {
display.dim(0);
dispLCD();
lcount = 0;
} else {
switch (key) {
case 10:
lcount = 0;
vol += VOL_DIF;
if (vol > VOL_MAX) vol = VOL_MIN;
radio.setVolume(vol);
dispLCD();
break;
case 1:
lcount = 0;
display.clearDisplay();
display.print("Key check");
display.display();
delay(1200);
if (digitalRead(SW1)) {
scan_freq = 0;
num += 1;
if (num >= STATION_NUM) num = 0;
freq = STATION[num];
radio.setMute(true);
radio.setFrequency(freq);
radio.waitAndFinishTune();
radio.setMute(false);
} else {
if (scan_freq != 1) radio.setFrequency(7600);
scan_freq = 1;
display.setCursor(0, 0);
display.setTextSize(2, 2 * SCREEN_MAG);
display.println("Scanning");
display.display();
/*
do {
radio.seek(0, 1, showFrequency); //continue seeking,seek up
radio.waitAndFinishTune();
delay(500); // RSSIを正常に読み込むため
rssi = radio.getRssi();
} while (rssi < RSSI_MIN );
*/
radio.setSeekThreshold(RSSI_MIN);
radio.seek(0, 1, showFrequency); //continue seeking,seek up
radio.waitAndFinishTune();
}
dispLCD();
break;
case 0:
lcount = 0;
if (scan_freq == 1) {
num = 100;
EEPROM.write(MEMORY2 + 1, int(freq / 100));
EEPROM.write(MEMORY2, freq % 100);
}
EEPROM.write(MEMORY0, num);
EEPROM.write(MEMORY1, vol);
display.dim(1);
delay(500);
display.dim(0);
break;
}
}
delay(1000);
if (lcount < 12000)lcount += 1;
if (lcount > DIMMER) display.dim(1); //画面を消灯
}
あまり余裕はないようですが、なんとか128×64で表示させることができました。1行間隔に表示されなくなり少し見やすい表示となりました。(縦2倍として表示数は変えないようにしました。)
ただ、このライブラリーはバグがあるようで、76–108 MHz (world wide)へのBAND切替がされません。(87–108 MHz 固定)こちらのライブラリーも少々修正が必要です。
C++を理解していないので本来の正しい書き方では無いとは思いますが、
void RDA5807::setBand(uint8_t band)
{
this->currentFMBand = band; //追加
reg03->refined.BAND = this->currentFMBand;
setRegister(REG03,reg03->raw);
}
/*
*/
void RDA5807::setSpace(uint8_t space)
{
this->currentFMSpace = space; //追加
reg03->refined.SPACE = this->currentFMSpace;
setRegister(REG03, reg03->raw);
}
これで期待する動作となりました。
ワイドFM DSPラジオ (RDA5807M)操作方法
このプログラムは、愛媛県松山市向けとなっていますので、使用する地域に合わせて、以下は修正します。
const uint16_t STATION[] = { 7970, 8770, 9170 }; //Station Tuned = 79.70 MHz.
const char* STATION_NAME[] = { "FM EHIME", "NHK FM", "RNB FM" } ;
const uint8_t STATION_NUM = 3;
オートスキャンの感度とステレオの切替感度は、以下で調整できます。
#define RSSI_MIN 45
#define RSSI_STEREO 50
プログラムも準備できたら、
電源はUSBを接続して電源供給すると勝手に起動します。
最初は、2つ目に登録した局が選択されます。
左のボタンSW1がボリューム、右のボタンSW0が選曲
SW0を長押しするとスキャン(Seek)して登録していない局も探せます。
SW0とSW1を同時押しして画面が一度消えたら現在の局をデフォルトとして記憶します。
とまぁ、こんな感じで最低限の機能ですが、使えます。
ちなみに節電?のため、最後の操作から約30sで表示が消えます。ボタンを押せば約1s以内に画面を表示、操作が可能となります。
今後
ひとまずこれはこれで完成ですが、思いの外に使えるので、マイコンをAVR単体かPICにしてもっと低消費電力に、電池も内蔵にしてお散歩時にポケットにいれて持ち運んだり、単体でスピーカ出力できるようにしたいと思いが広がる工作でした。
これは、次期検討実験や据え置きチューナー的に使用しています。
購入したものは、以下