ESP32-S3 + PCM5102でWeb Radio 情報表示 ― 2025/08/28
局名や流れている曲名を表示できないかと探していたら、ライブラリの出所
https://github.com/schreibfaul1/ESP32-audioI2S/blob/master/README.md
に、コールバック関数を登録すると、適時得られた情報をとってきてくれる機能がありました。
Audio::audio_info_callback = my_audio_info; // optional// callbacks void my_audio_info(Audio::msg_t m) { Serial.printf("%s: %s\n", m.s, m.msg); }
この中身を変えれば欲しいものがとれそうです。
下のほうにdetailed cb というのがあるので、これを使うと、シリアルモニタに
info: ....... connect to: "stream.laut.fm" on port 80 path "/animefm"
info: ....... Connection has been established in 329 ms
last URL: ... stream.laut.fm/animefm
stream.laut.fm/animefm
info: ....... redirect to new host "http://animefm.stream.laut.fm/animefm?t302=2025-08-28_04-10-32&uuid=929bcae0-518f-486b-8b6a-bc4eca79ef00"
info: ....... next URL: "http://animefm.stream.laut.fm/animefm?t302=2025-08-28_04-10-32&uuid=929bcae0-518f-486b-8b6a-bc4eca79ef00"
station name: Anime FM
icy URL: .... http://laut.fm/animefm
icy descr: .. Anime Openings, J/K-Pop, JRock...
info: ....... icy-genre: Ending
bitrate: .... 128000
info: ....... MP3Decoder has been initialized
info: ....... stream ready
info: ....... syncword found at pos 0
info: ....... MPEG-1 Layer III
info: ....... Channels: 2
info: ....... SampleRate: 44100
info: ....... BitsPerSample: 16
info: ....... BitRate: 128000
info: ....... Stream URL; StreamUrl='252593'
stream title: Toyasaki Aki, Hisaka Youko, Satou Satomi, Kotobuki Minako - Happy!? Sorry!!
と、情報が来るたび表示されます。
見てると日本語のタイトルも来る場合もあります。
日本語表示できるように U8g2_for_Adafruit_GFX を組み込んで、アップデートしました。
無い漢字が多々あるようです。。。
スケッチはごちゃごちゃになったので、整理中です。
ESP32-S3 + PCM5102 でWeb Radio 続き ― 2025/08/24
SSD1306をつないで、アップデートしました。
ネットや LM Stadio に教えてもらったコードを使いました。
ネットラジオのURLはfoobar2000で検索して、聴けるものを入れてみました。
ビットレートが128Kを超えると、音が途切れます。
●スケッチ
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFiMulti.h>
#include "WiFi.h"
#include "Audio.h"
#include "Rotary.h"
enum STATE { NORMAL, MENU, VOLUME, FUNCTION };
STATE state = NORMAL;
// Enconder PINs
#define ENCODER_PIN_A 1
#define ENCODER_PIN_B 2
#define PUSH_SW 21
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
const uint8_t I2C_SDA = 18; //
const uint8_t I2C_SCL = 17; //
#define SCREEN_WIDTH (128)
//#define SCREEN_HEIGHT (64)
#define SCREEN_HEIGHT (32)
#define VUPOS_X (96)
#define VUPOS_Y (1)
#define VOLPOS_X (8)
#define VOLPOS_Y (22)
// 8×8 のビットマップ(例:アンテナアイコン)
const uint8_t antena[8] PROGMEM = {
0b11111110,
0b01010100,
0b00111000,
0b00010000,
0b00010000,
0b00010000,
0b00010000,
0b00000000
};
const uint8_t antenaNG[8] PROGMEM = {
0b11111110,
0b11010110,
0b00111000,
0b01010000,
0b00111000,
0b01010100,
0b10010010,
0b00000000
};
const uint8_t antena0[8] PROGMEM = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000
};
const uint8_t antena1[8] PROGMEM = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b01000000,
0b00000000
};
const uint8_t antena2[8] PROGMEM = {
0b00000000,
0b00000000,
0b00000000,
0b00000000,
0b00010000,
0b00010000,
0b01010000,
0b00000000
};
const uint8_t antena3[8] PROGMEM = {
0b00000000,
0b00000000,
0b00000100,
0b00000100,
0b00010100,
0b00010100,
0b01010100,
0b00000000
};
const uint8_t antena4[8] PROGMEM = {
0b00000001,
0b00000001,
0b00000101,
0b00000101,
0b00010101,
0b00010101,
0b01010101,
0b00000000
};
// 8×8 のビットマップ(例:アンテナアイコン)
const uint8_t wifi_ant[8] PROGMEM = {
0b01111100,
0b10000010,
0b00111000,
0b01000100,
0b00010000,
0b00101000,
0b00010000,
0b00000000
};
#define OLED_RESET (-1)
#define SCREEN_ADDRESS (0x3C)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Digital I/O used
# define I2S_DOUT 11 // DIN connection
# define I2S_BCLK 13 // Bit clock
# define I2S_LRC 12 // Left Right Clock
Audio audio;
String ssid = "xxxxxxxxxxxxxxxx";
String password = "xxxxxxxxxxxxxxxx";
String stations[] ={
"radio-stream.nhk.jp/hls/live/2023229/nhkradiruakr1/master.m3u8", //NHK 1
"radio-stream.nhk.jp/hls/live/2023501/nhkradiruakr2/master.m3u8", //NHK 2
"radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8", //NHK FM
"stream.laut.fm/animefm",
"cast1.torontocast.com:2120/;.mp3",
"cast1.torontocast.com:2170/;.mp3",
// "cast1.torontocast.com/JapanHits",
"cast1.torontocast.com:2120/stream",
"s3.radio.co/sc8d895604/listen",
"kathy.torontocast.com:3060/;?shoutcast",
"vrx.piro.moe:8000/stream-256",
"vocaloid.radioca.st/stream", //Vocaloid Radio
"mtist.as.smartstream.ne.jp/30043/livestream/chunklist.m3u8", //MWave
"mtist.as.smartstream.ne.jp/30081/livestream/chunklist.m3u8", //
"ice1.somafm.com/illstreet-128-mp3", // SomaFM / Illinois Street Lounge
"ais-sa2.cdnstream1.com/b22139_128mp3", // 101 SMOOTH JAZZ
"relax.stream.publicradio.org/relax.mp3", // Your Classical - Relax
"ice1.somafm.com/secretagent-128-mp3", // SomaFM / Secret Agent
"ice1.somafm.com/seventies-128-mp3", // SomaFM / Left Coast 70s
"ice1.somafm.com/bootliquor-128-mp3", // SomaFM / Boot Liquor
"shonanbeachfm.out.airtime.pro:8000/shonanbeachfm_a", // FM Blue Shonan (FM・ブルー湘南 , JOZZ3AD-FM, 78.5 MHz, Y...
"0n-80s.radionetz.de:8000/0n-70s.mp3",
"mediaserv30.live-streams.nl:8000/stream",
"mp3.ffh.de/radioffh/hqlivestream.aac", // 128k aac
"listen.rusongs.ru/ru-mp3-128",
};
uint8_t cur_station = 0; // current station No.
uint8_t num_elements = sizeof(stations) / sizeof(stations[0]);
volatile int encoderCount = 0;
// Encoder control
Rotary encoder = Rotary(ENCODER_PIN_A, ENCODER_PIN_B);
//Wifi
WiFiMulti wifiMulti;
/*
Reads encoder via interrupt
Use Rotary.h and Rotary.cpp implementation to process encoder via interrupt
*/
void rotaryEncoder()
{ // rotary encoder events
uint8_t encoderStatus = encoder.process();
if (encoderStatus)
encoderCount = (encoderStatus == DIR_CW) ? 1 : -1;
}
//
//scroll text
//
int x, minX;
void scrollText(String message) {
minX = -12 * message.length(); //12 = 6 pixels/character * text size 2
display.setTextSize(2);
display.setCursor(x,15);
display.print(message);
display.display();
// x = x - 2; // scroll speed
x = x - 1; // scroll speed slow
if (x < minX) x = display.width();
}
void setup() {
rgbLedWrite(RGB_BUILTIN, RGB_BRIGHTNESS, 0, 0); // Red
pinMode(ENCODER_PIN_A, INPUT_PULLUP);
pinMode(ENCODER_PIN_B, INPUT_PULLUP);
pinMode(PUSH_SW, INPUT_PULLUP);
Serial.begin(115200);
// while (!Serial); // シリアルモニタが開くまで待つ
delay(10);
//setup I2C
Wire.begin(I2C_SDA, I2C_SCL);
//setup SSD1306
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
for(;;);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(0, 0);
display.print(F(" ESP32-S3 "));
display.setTextSize(1);
display.setCursor(0, 15);
display.println(F(" WebRadio V1.0"));
display.print(F(" 2025/8/24"));
display.display();
Serial.println("SSD1306 OK");
#if 0
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED) delay(1500);
Serial.println("WiFi start");
#else
WiFi.disconnect();
WiFi.mode(WIFI_STA);
/*Type all known SSID and their passwords*/
wifiMulti.addAP("xxxxxxxxxxx", "xxxxxxxxxxxxx"); /*Network 1 we want to connect*/
wifiMulti.addAP("xxxxxxxxxxx","xxxxxxxxxxxxxx"); /*Network 2 we want to connect*/
// WiFi.scanNetworks will give total networks
int n = WiFi.scanNetworks(); /*Scan for available network*/
Serial.println("scan done");
if (n == 0) {
Serial.println("No Available Networks"); /*Prints if no network found*/
}
else {
Serial.print(n);
Serial.println(" Networks found"); /*Prints if network found*/
for (int i = 0; i < n; ++i) {
Serial.print(i + 1); /*Print the SSID and RSSI of available network*/
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
delay(10);
}
}
/*Connects to strongest available defined network with SSID and Password available*/
Serial.println("Connecting to Wifi...");
#if 0
if(wifiMulti.run() == WL_CONNECTED) {
Serial.println("");
Serial.print("Connected to WIFI Network: ");
Serial.println(WiFi.SSID());
Serial.print("IP address of Connected Network: ");
Serial.println(WiFi.localIP()); /*Prints IP address of connected network*/
rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0); // Green
}
#else
while (WiFi.status() != WL_CONNECTED) {
wifiMulti.run();
// delay(1000);
if (WiFi.status() == WL_CONNECTED) {
Serial.println("");
Serial.print("Connected to WIFI Network: ");
Serial.println(WiFi.SSID());
Serial.print("IP address of Connected Network: ");
Serial.println(WiFi.localIP()); /*Prints IP address of connected network*/
rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0); // Green
}
else {
Serial.println("Wi-Fi Nothing!\nRetry!!");
}
}
#endif
#endif
//Audio
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(12); // 0...21
rgbLedWrite(RGB_BUILTIN, 0, 0, RGB_BRIGHTNESS); // Blue
Serial.println("audio start");
//set first station
audio.connecttohost(stations[cur_station].c_str());
Serial.println(stations[cur_station].c_str());
//display station
/* ---------- スクロール設定 ---------- */
display.setTextSize(2);
display.setTextColor(WHITE);
display.setTextWrap(false);
x = display.width();
// Encoder interrupt
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), rotaryEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), rotaryEncoder, CHANGE);
}
void loop() {
// put your main code here, to run repeatedly:
// static int push_key = 0;
#if 1
static unsigned long lastUpdate = 0;
const uint32_t UPDATE_MS = 10000; //
if (millis() - lastUpdate < UPDATE_MS) {
;
}
else {
lastUpdate = millis();
state = NORMAL;
// Serial.println(lastUpdate);
}
#endif
//audio
audio.loop();
//display wifi rssi
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected!");
display.drawBitmap(0, 0, antenaNG, 8, 8, SSD1306_WHITE);
}
display.clearDisplay();
#if 1
display.drawBitmap(0, 0, antena, 8, 8, SSD1306_WHITE);
int rssi = WiFi.RSSI();
if (rssi > -55) {
display.drawBitmap(8, 0, antena4, 8, 8, SSD1306_WHITE);
}
else if (rssi > -67){
display.drawBitmap(8, 0, antena3, 8, 8, SSD1306_WHITE);
}
else if (rssi > -70){
display.drawBitmap(8, 0, antena2, 8, 8, SSD1306_WHITE);
}
else if (rssi > -80){
display.drawBitmap(8, 0, antena1, 8, 8, SSD1306_WHITE);
}
else {
display.drawBitmap(8, 0, antena0, 8, 8, SSD1306_WHITE);
}
#else
display.drawBitmap(0, 0, wifi_ant, 8, 8, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(8, 0);
display.print(WiFi.RSSI());
display.print("dBm ");
#endif
//display volume
display.setTextSize(1);
display.setCursor(80, 0);
display.print(audio.getVolume());
//displsy audio level
// Convert to bar width (0..32)
uint8_t barWidth = constrain((uint16_t)(audio.getVUlevel()/1024), 0, SCREEN_WIDTH / 4);
uint8_t barHeight = SCREEN_HEIGHT / 8; //level meter hight
display.fillRect(VUPOS_X, VUPOS_Y, barWidth, barHeight, WHITE);
// Optional: draw border
display.drawRect(VUPOS_X-1, VUPOS_Y-1, 32+1, barHeight+2, WHITE);
switch (state) {
case NORMAL:
// Check if the encoder has moved.
if (encoderCount != 0)
{
if (encoderCount == 1)
{
rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0); // Green
Serial.println("Encoder up");
cur_station = (cur_station == num_elements - 1) ? 0 : (++cur_station);
}
else
{
rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0); // Green
Serial.println("Encoder down");
cur_station = (cur_station == 0) ? (num_elements - 1) : (--cur_station);
}
encoderCount = 0;
audio.connecttohost(stations[cur_station].c_str());
Serial.println(stations[cur_station].c_str());
x = display.width();
rgbLedWrite(RGB_BUILTIN, 0, 0, RGB_BRIGHTNESS); // Blue
}
else if (digitalRead(PUSH_SW) == LOW) {
Serial.println("Push");
vTaskDelay(500);
state = VOLUME;
lastUpdate = millis();
}
//display codec & bit ratio
display.setTextSize(1);
display.setCursor(0, 8);
display.print(audio.getCodecname());
display.print(":");
display.print(audio.getBitRate(false));
display.print("bps");
//display station
scrollText(F(stations[cur_station].c_str()));
break;
case VOLUME:
int vol = audio.getVolume();
//display volume control
display.setTextSize(1);
display.setCursor(32, 8);
display.print("Volume");
display.fillRect(VOLPOS_X, VOLPOS_Y, vol * 4, 8, WHITE);
// Optional: draw border
display.drawRect(VOLPOS_X-1, VOLPOS_Y-1, 21 * 4 + 1, 8 + 2, WHITE);
// Check if the encoder has moved.
if (encoderCount != 0)
{
if (encoderCount == 1)
{
Serial.println("Encoder up");
audio.setVolume((vol < 21) ? ++vol : 21); // 0...21
}
else
{
Serial.println("Encoder down");
audio.setVolume((vol > 0) ? --vol : 0); // 0...21
}
lastUpdate = millis();
}
else if (digitalRead(PUSH_SW) == LOW) {
Serial.println("Push");
state = NORMAL;
vTaskDelay(500); //チャタリング対策
}
encoderCount = 0;
display.display();
break;
}
vTaskDelay(1);
}
※適当に作成しているので、間違い、不具合があるかもしれません。
ESP32-S3 + PCM5102 でWeb Radio ― 2025/08/16
電子部品がアリエクで安いので、いろいろと買ってみました。
で、インターネットラジオを作ってみました。
ESP32-S3-N16R8 \560
PCM5102A DAC デコーダ GY-PCM5102 \340
ESP32-S3-N16R8
PCM5102A DAC デコーダボードの設定
●はんだブリッジ
H1L → L
H2L → L
H3L → H
H4L → L
●SCK はんだブリッジ
左上にあるパターン。GNDに落としているのでSCKをGNDにつないでもOK
配線(I2S接続)
PCM5102 → ESP32-S3(基板に書いてある番号)
VIN → 3.3V
GND → GND
LCK → 12
DIN → 11
BCK → 13
SCK → GND または、はんだブリッジしていれば OPEN
LCK,DIN,BCK は適当に好きなところにつないだだけで、ほかでもいいのかも。。
arduino ide の設定
最初、ボードの設定を気にせずいたらいろいろとエラーが出ました。
(I2Sの配線の場所が悪いのかと思い、いろいろかえたりしましたが、これが原因ではなくメモリが足りないだけでした。)
●フラッシュの容量が足りないエラー → フラッシュの容量を増やすには、ツールの Partition Scheme: を Huge APP に
●実行すると、シリアルモニタにOOM: failed to allocate xxxxx bytes エラー(バッファーアロケーションメモリ足りない)が出る → Tools->PSRAM->OPI PSRAM に設定
スケッチ
ネットにあったものを参考にしました。
SSID & Password 自宅のWifiに
とりあえず放送局は、NHK
ここから
#include <Wire.h>
#include "WiFi.h"
#include "Audio.h"
// PCM5102A
# define I2S_DOUT 11 // DIN connection
# define I2S_BCLK 13 // Bit clock
# define I2S_LRC 12 // Left Right Clock
Audio audio;
//SSID & Password
String ssid = "xxxxxxxx";
String password = "xxxxxxxx";
String stations[] ={
"https://radio-stream.nhk.jp/hls/live/2023229/nhkradiruakr1/master.m3u8", //NHK 1
"https://radio-stream.nhk.jp/hls/live/2023501/nhkradiruakr2/master.m3u8", //NHK 2
"https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8", //NHK FM
};
uint8_t cur_station = 0; // current station No.
uint8_t num_elements = sizeof(stations) / sizeof(stations[0]);
void setup() {
//setup serial
Serial.begin(115200);
//setup Wifi
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED) delay(1500);
Serial.println("WiFi start");
//setup audio
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(12); // 0...21
Serial.println("audio start");
//set Radio Station
audio.connecttohost(stations[cur_station].c_str());
Serial.println(stations[cur_station].c_str());
}
void loop() {
vTaskDelay(1);
audio.loop();
}
ここまで
GnuRadio ― 2025/07/20
SDR++でラジオが聴けるようになったので、信号処理のブロックを並べてラジオが作れるというGnuRadioを使ってみました。
radioconda-Windows-x86_64.exe をインストールしました。
↑この画面で結構待たされました。

Radiocondaを使うと以下のものがインストールされるようです。
・Digital RF
・GNU Radio (including an increasing list of out-of-tree modules)
・gqrx
・inspectrum
これだけで使えるようになりました。
ネットの情報を色々見て作ってみました。ネット情報だと最初のrtl-sdr sourceが、osmosdrを使っていることが多いのですが、最初FedoraでGnuRadioを使って動かしたら、なぜかosmoではエラーになってしまったので、soapysdrを使いました。これをそのままWindowsで動かしました。
Windowsではosmosdrも問題ないです。
SDR ラジオ ― 2025/07/18
以前に仕事でTV信号の復調処理のDSPプログラムをやったことがあって、ソフトウエアラジオに興味を持ちました。でアマゾンで Nooelec RTL-SDR v5 SDR - NESDR SMArt を¥6695で買ってみました。

ドライバーのインストールは製品のサイトにある通り行いました。が、説明とちょっと違って、optionでall devideを選択しなくてもNooelec SMArt v5が出てきました。画像はインストール後です。最初は左のDriverが"none"でした。
最初デバイスマネージャーは"!"でした。
試しにSDR++を動かしてみました。
サイトからZipファイルをダウンロードして解凍EXEファイルをつつくだけで起動しました。
ですが、適当な場所からだと周波数の文字が小さくなってしまったので、サイトにあったマニュアル通りに C:\SDRPPフォルダを作ってすべてのファイルをここに入れました。
FMを聞くには
source:RTL-SDR
direct sampling:disable
あとゲインの設定やら、ラジオにはあまり詳しくないので、適当に画像のような設定でなんとか聞くことができました。
パソコンに近いとノイズが乗るようなので、100均で買ったUSB延長ケーブルでつなぎました。
最初問題があるかと思いましたが、今のところ受信状態は良好です。
アンテナはアリエクスプレスで買ったYouLoopというもののまがい品です。
動作環境
OS:Windows11
パソコン:GMKtec K8 Plus
N100搭載のミニPCでも動きましたが、ウインドウを切り替えたりすると音が途切れました。














最近のコメント