はじめに
今回はM5Stack Japan Creativity Contest 2023にチャレンジするために、実用的に使える作品で挑んでみるべく、1チップでFMとAMが受信可能なICであるKT Micro社のKT0913を使ってM5STACKにスタック出来るラジオモジュールを製作してみました。

このICは32.768KHz(他の周波数でも可)水晶振動子と少しの周辺回路定数があれば簡単に動かすことができるので、このICを採用したラジオ製作キットが市販されていても良いように思いました。
まずはICの動作確認
手始めにICの動作確認のためデータシートを参照しながらブレッドボードで回路を配線してみました。

ジャンパーワイヤーでの配線のためか音質はかなり悪いですが、問題なく動作することは確認できたので、自作のプリント基板を起こすことにしました。
ちなみに、ブレッドボードが若干小さめなので、私的にはもう少し大きいほうが良かったように思いますが、このような便利グッズを使用するとちょっとした実験がやり易くて開発が捗りますね。
回路図
以下に示す回路図ではM5STACKのボタンの操作だけでは無くアナログ的な操作も試してみたかったので、周波数チューニングと音量の可変はダイアル式ボリューム(可変抵抗)も使用出来る設計としました。KT0913は、ハードウェアが有する機能として、周波数チューニングと音量調整のために、可変抵抗を使う方式が選択できる様になっています。
もちろん、M5STACKのプロセッサであるESP32とI2Cの制御ラインで接続しているので、KT0913のレジスタを叩くことで周波数など変更することも可能となっており、M5STACKの3つボタンで周波数や音量を変更するようなプログラムを組めば、ボリューム(可変抵抗)は無くても問題ありません。
ICを動かすための最低限の回路であれば難しいところは特に無くて、強いて言えば、メインICの発振回路用に水晶振動子を使用する点と、今回の設計ではイヤホンのGNDラインをFMアンテナと兼用にしていますので、FM信号をGNDに流れないようにするため交流阻止用の大容量マイクロインダクタが必要なところには注意が必要です。私は手元にあった2.2uHを使用しましたが良好に受信しました。インダクタのセット売りはあまり見かけませんので、あらかじめ手元に一式準備していると最適定数がカットアンドトライで検討できて便利なのでお勧めします。
部品は回路図に部品番号を記載しているものを秋月電子さんから購入しました。

基板パターン図面
今回、全部品を手半田付けしましたが、一番の難関は、KT0913のピンピッチは0.635mmと挟ピッチでしたので半田ブリッジが発生してしまい修正にかなり苦戦したところです。なお、フラックスを使用する事で手半田でも比較的ハンダ付けがやり易くなります。
自身で基板設計を行った上で手半田での部品実装する場合、 5cm角の面積があれば、部品レイアウトに関してはスペース的に余裕があるので、表面実装部品を使用するよりも挿入部品の抵抗やコンデンサで設計して組み上げたほうがアッセンブリは簡単と思います。
表面(部品面)
M5STACK用モジュールとして仕上げるには基本的に部品は片面実装である必要がありますが、部品点数は少なく、すべての部品を表面に配置することは簡単にできました。

裏面(半田面)
挿入部品の半田面となります。挿入部品のスルーホールとおもて面で配線出来なかった電源および可変抵抗への配線、及びstack用のピンヘッダを実装したGND面としています。

今回の基板作成にはPCBWayさんにご協力いただきました。ガーバーデータ出力から一週間程度で手元に基板が届きます。よろしければこちらのブログで発注方法などを紹介していますのでご参照下さい。
動作状態を確認
FM放送の音質を動画で確認できます。ダイヤルを回して周波数を変更しながら撮影しました。
動画ではわかりにくいかもしれませんが、結構ノイズも少なく十分使用できるものが作成できました。
参考プログラム
動画撮影時は動作検証中のためRSSIやSNRなど表示していますが、数字を表示しても実使用上あまり意味がなかったので、現在選択している周波数とボリュームの数字を大きく表示して視認性を良くしたプログラム(Arduino sketch)が以下になります。BボタンでFMとAMの切替ができます。
#include <M5Unified.h>
#include <Wire.h>
uint16_t data ;
unsigned char num_byte = 2;
unsigned char DEVICE_ADDRESS = 0x35;
uint8_t REGISTER_ADDRESS;
String FMMODE;
String AM_FM;
//レジスタアドレス
unsigned char CHIP_ID = 0x01;
unsigned char SEEK = 0x02;
unsigned char TUNE = 0x03; //FM Channel
unsigned char VOLUME = 0x04; //Softmute / Mute / Bass Boost Efect / Audio DAC Anti-pop
unsigned char DSPCFGA = 0x05; //Mono
unsigned char LOCFGA = 0x0A;
unsigned char LOCFGC = 0x0C;
unsigned char RXCFG= 0x0F; // Standby mode / Volume Control
unsigned char STATUSA = 0x12; //RSSI
unsigned char STATUSB = 0x13;
unsigned char STATUSC = 0x14; //SNR
unsigned char AMSYSCFG = 0x16; // AM/FM mode Control / Audio Gain Selection
unsigned char AMCHAN = 0x17; // AM Channel Setting
unsigned char AMCALI = 0x18; //On Chip Capacitor for AM
unsigned char GPIOCFG = 0x1D; // Vol Pin Mode Selection, CH Pin Mode Selection
unsigned char AMDSP = 0x22; // AM Channel Bandwidth Selection
unsigned char AMSTATUSA = 0x24; // AM Channel RSSI
unsigned char AMSTATUSB = 0x25;
unsigned char SOFTMUTE = 0x2E; // Softmute Attenuation
unsigned char USERSTARTCH = 0x2F;
unsigned char USERGUARD = 0x30;
unsigned char USERCHANNUM = 0x31;
unsigned char AMCFG = 0x33; //AM Channel Space Selection , Working mode selections when key mode is selected.
unsigned char AMCFG2 = 0x34;
unsigned char VOLGUARD = 0x3A;
unsigned char AFC = 0x3C;
//
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.setCursor(80, 0);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.print("DSP RADIO");
Serial.println("KT0913 RADIO");
Wire.begin();
fm_mode();
REGISTER_ADDRESS = GPIOCFG; //Dial mode VOL amd CH
data = 0B0000000000001010; // VOL = 10 / CH = 10
I2CsendMULTIbyte();
}
void I2CsendMULTIbyte(){
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.write(REGISTER_ADDRESS);
Wire.write(highByte(data));
Wire.write(lowByte(data));
Wire.endTransmission();
}
void I2CreadMULTIbyte(){
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.write(REGISTER_ADDRESS);
Wire.endTransmission(false);
Wire.requestFrom(DEVICE_ADDRESS,num_byte);
while (Wire.available() < num_byte);
uint8_t Hb = Wire.read();
uint8_t Lb = Wire.read();
data = (Hb << 8) | Lb;
}
void fm_mode(){
REGISTER_ADDRESS = AMSYSCFG;
I2CreadMULTIbyte();
uint16_t amsyscfg_mode = data;
data = 0B0100000000000010;
I2CsendMULTIbyte();
uint16_t am_fm = (data & 0B1000000000000000) >> 15;
if (am_fm == 1) AM_FM ="AM";
else AM_FM ="FM";
M5.Display.setCursor(145, 210);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.print("AM");
Serial.printf("AM/FM: %S\n", AM_FM);
REGISTER_ADDRESS = STATUSA;
I2CreadMULTIbyte();
uint16_t statusa = data;
uint16_t fmmode = (statusa & 0B000001100000000) >> 8;
if (fmmode == 3) FMMODE ="STEREO";
else FMMODE ="MONO";
Serial.printf("FMMODE: %S\n", FMMODE);
REGISTER_ADDRESS = AMSYSCFG;
I2CreadMULTIbyte();
uint16_t amsyscfg_band = data;
data = amsyscfg_band | 0B0100000000000000;
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERSTARTCH;
data = 0B0000010111110000; // 76MHz = 1520 * 50kHZ (1520= 5F0h)
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERGUARD;
data = 0B0000000000010111; //23ch 2kohm
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERCHANNUM;
data = 0B0000000010110101; //181ch 76MHz - 94MHz 100KHz/step 8kohm
I2CsendMULTIbyte();
}
void am_mode(){
REGISTER_ADDRESS = AMSYSCFG;
I2CreadMULTIbyte();
uint16_t amsyscfg_mode = data;
data = 0B1000000000000010;
I2CsendMULTIbyte();
uint16_t am_fm = (data & 0B1000000000000000) >> 15;
if (am_fm == 1) AM_FM ="AM";
else AM_FM ="FM";
M5.Display.setCursor(145, 210);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.print("FM"); // 1NO TOKI AM
Serial.printf("AM/FM: %S\n", AM_FM);
REGISTER_ADDRESS = AMSYSCFG;
I2CreadMULTIbyte();
uint16_t amsyscfg_band = data;
data = amsyscfg_band | 0B0100000000000000;
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERSTARTCH;
data = 0B0000001000001110; // 526kHz = 526 * 1kHZ (526= 20Eh)
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERGUARD;
data = 0B0000000100010010; //274ch 2kohm
I2CsendMULTIbyte();
REGISTER_ADDRESS = USERCHANNUM;
data = 0B0000010001000111; //1095ch 526kHz ~ 1620kHz ch_space = 1KHz/step 8kohm
I2CsendMULTIbyte();
}
void loop() {
delay(1);
M5.update();
REGISTER_ADDRESS = CHIP_ID;
I2CreadMULTIbyte();
uint16_t chipID = data;
Serial.printf("chipID: %x\n", chipID);
REGISTER_ADDRESS = STATUSA;
I2CreadMULTIbyte();
uint16_t statusa = data;
uint16_t fmrssi = (statusa & 0B000000011111000) >> 3;
int16_t FMRSSI = -100+fmrssi*3;
uint16_t fmmode = (statusa & 0B000001100000000) >> 8;
if (fmmode == 3) FMMODE ="STEREO";
else FMMODE ="MONO";
Serial.printf("RSSI: %d\n", FMRSSI);
Serial.printf("FMMODE: %S\n", FMMODE);
REGISTER_ADDRESS = STATUSC;
I2CreadMULTIbyte();
uint16_t statusc = data;
uint16_t snr = (statusc & 0B0001111111000000) >> 6;
Serial.printf("SNR: %d\n", snr);
REGISTER_ADDRESS = RXCFG;
I2CreadMULTIbyte();
uint16_t rxcfg = data;
uint16_t volume = rxcfg & 0B0000000000011111;
M5.Display.setCursor(205, 50);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.printf("VOLUME");
M5.Display.setCursor(215, 85);
M5.Display.setFont(&fonts::Font7);
M5.Display.setTextSize(1);
M5.Display.printf("%03d", volume*100 / 31);
M5.Display.setCursor(295, 150);
M5.Display.setTextSize(3);
M5.Display.setFont(&fonts::Font0);
M5.Display.print("%");
M5.Display.setFont(&fonts::Font0);
if (AM_FM == "FM"){
REGISTER_ADDRESS = TUNE;
I2CreadMULTIbyte();
uint16_t tune = data;
uint16_t fm_freq = tune & 0B0000111111111111;
M5.Display.setCursor(10, 50);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.printf("%S FREQ", AM_FM);
M5.Display.setCursor(25, 85);
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::Font7);
M5.Display.printf("%03.1f", (float)fm_freq/1000*50);
M5.Display.setCursor(82, 150);
M5.Display.setTextSize(3);
M5.Display.setFont(&fonts::Font0);
M5.Display.print("MHz");
Serial.printf("FM_FREQ: %.1f\n MHz", (float)fm_freq/1000*50);
M5.Display.setFont(&fonts::Font0);
}
else if (AM_FM == "AM"){
REGISTER_ADDRESS = AMCHAN;
I2CreadMULTIbyte();
uint16_t amchan = data;
uint16_t am_freq = amchan & 0B0000011111111111;
M5.Display.setCursor(10, 50);
M5.Display.setFont(&fonts::Font0);
M5.Display.setTextSize(3);
M5.Display.printf("%S FREQ", AM_FM);
M5.Display.setCursor(5, 85);
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::Font7);
M5.Display.printf("%04d", am_freq);
M5.Display.setCursor(82, 150);
M5.Display.setTextSize(3);
M5.Display.setFont(&fonts::Font0);
M5.Display.print("KHz");
Serial.printf("AM_FREQ: %d\n", am_freq);
}
if (M5.BtnB.wasPressed() && (AM_FM == "FM") ){
am_mode();
M5.Display.setCursor(0, 85);
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::Font7);
M5.Display.print(" ");
}
else if (M5.BtnB.wasPressed() && (AM_FM == "AM") ){
fm_mode();
M5.Display.setCursor(0, 85);
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::Font7);
M5.Display.print(" ");
}
}
おわりに
今回試作した基板ではFM放送の音質はかなりクリアに聞こえますが、残念ながらAM放送はノイズばかりで聞き取れる状態になりませんでした。ただし、アンテナを指で触ると放送が聞きとれるようになるので同調が上手くいっていないのか、IC内部のアンプが異常発振しているのかもしれません。 AM放送の受信不具合の原因を探していると、アンテナをM5STACK本体から離すと音声が比較的クリアに聞ける状態になるので、結論的にはM5STACK本体(特に液晶?)のノイズが原因と思われることがわかりました。また液晶画面が付属していないATOM S3でも試してみましたが、AMアンテナをATOM S3本体に近づけるとノイズが大きくなります。と言うことは、デコデコのスイッチングノイズの可能性が大きいと思います。完全な原因の特定はできていませんが、結論的にはAMアンテナはマイコン本体から5cmほど離す必要があることがわかりました。今後、さらに改善ができるのか検討してみたいと思います。
また、画面についても、もっとかっこ良くしていきたいと思っています。
同系列のKT0936とKT0937でもFM放送を受信することができましたので近々ご紹介したいと思います。こちらの記事で紹介していますのでご参照下さい。
KT0936の記事:https://massa4649.com/kt0936_1/
KT0937の記事:https://massa4649.com/kt0937_1/
ご参考までに、基板のみをBOOTHで頒布していますのでご興味のある方はどうぞ。
設計を見直した2ndEditionの記事も公開していますのでよろしくお願いします。(2025年2月)




コメント