LM Studioでスケッチの改善2025/08/29

前回作成した Web Radio のスケッチで、局名と曲名を2段、スクロールするのに同じコードを2つ書いていて無駄だなと思っておりました。C++でかっこよく複数使える関数にしたいと思い、LM Studio というローカルで使えるAIに聞いてみました。LLMは gpt-oss-20b を使いました。


意図が分かってもらえたようで、良さそうな回答が得られました。
ただグラフィックまわりは変だったので直してこんな感じでできました。
ボードはESP32-C3 & 128x32 SSD1306 Display
できたスケッチはこちら
ScrollText.h
/*********************************************************************
 *  ScrollText.h
 *********************************************************************/
#pragma once

#include <Adafruit_GFX.h>          // 既存の画面描画ライブラリ
#include <U8g2_for_Adafruit_GFX.h>

// ------------------------------------------------------------------
// 一行分をスクロールさせる構造体
struct ScrollItem {
    String text;          // 表示したい文字列
    int   x;              // 現在のX座標(左端)
    int   y;              // Y座標(固定で1行ごとにずらす)
    int   speed;          // 1フレームあたりのスクロール量 (px)
    int   minX;           // テキストが画面外に完全に出る位置
    const uint8_t *font;  // フォントポインタ(U8g2フォント)
};

// ------------------------------------------------------------------
// 複数行を管理するクラス
class Scroller {
public:
    Scroller(U8G2_FOR_ADAFRUIT_GFX &gfx, int displayW)
        : gfx(gfx), width(displayW) {}

    // 1行追加。y は任意に決めるか、内部で自動計算する。
    void addText(const String& txt,
                 int y,
                 int speed = -2,
                 const uint8_t *font = u8g2_font_unifont_t_japanese1) {
        ScrollItem itm;
        itm.text  = txt;
        itm.y     = y;
        itm.speed = speed;
        itm.font  = font;

        // 初期位置: 画面右端
        itm.x = width;

        // 最小X(テキストが完全に左側へ消える)=文字数 * 6(px) + 1px余白
        //   ※ 6 px はフォントの幅。必要なら変更。
        itm.minX = - (int)(txt.length() * 6);

        items.push_back(itm);
    }

    // 1フレーム分更新+描画
    void update() {
        for (auto &itm : items) {
            // フォント設定
            gfx.setFontMode(1);          // 透明背景
            gfx.setFont(itm.font);

            // 描画位置を設定
            gfx.setCursor(itm.x, itm.y);
            gfx.print(itm.text);

            // スクロール処理
            itm.x += itm.speed;   // speed は負数で左へ

            // 画面外に完全に出たら右側へ戻す(ループ再表示)
            if (itm.x < itm.minX) {
                itm.x = width;
            }
        }
    }

private:
    U8G2_FOR_ADAFRUIT_GFX &gfx;          // 画面描画オブジェクト
    int                     width;       // 画面幅(px)
    std::vector<ScrollItem> items;       // 複数行管理
};


本体
#include <Adafruit_SSD1306.h>
#include <U8g2_for_Adafruit_GFX.h>
#include "ScrollText.h"

const uint8_t I2C_SDA = 0;   //   for C3mini
const uint8_t I2C_SCL = 1;   //

#define SCREEN_WIDTH 128
//#define SCREEN_HEIGHT 64
#define SCREEN_HEIGHT 32
#define OLED_RESET     -1
#define SCREEN_ADDRESS      (0x3C)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

Scroller scroller(u8g2_for_adafruit_gfx, SCREEN_WIDTH);

void setup() {
    Serial.begin(115200);

      Wire.begin(I2C_SDA, I2C_SCL);

    if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { // I²Cアドレス
        Serial.println(F("SSD1306 allocation failed"));
        for (;;); // 無限ループ
    }
    u8g2_for_adafruit_gfx.begin(display);                 // connect u8g2 procedures to Adafruit GFX

    display.clearDisplay();

    // 例: 2 行スクロール
#if 0
    scroller.addText(F("Hello World! This is line 1."), 16);
    scroller.addText(F("Second line scrolling left."), 32, -3);
#else
    scroller.addText(F("Hello World! This is line 1."), 16);
    scroller.addText(F("日本語テスト"), 32-1, -3, u8g2_font_unifont_t_japanese1);
#endif
    // 任意にフォント変更したい場合は第4引数で指定
    // scroller.addText(F("日本語テスト"), 48, -2,
    //                  u8g2_font_unifont_t_japanese1);
}

void loop() {
    Serial.println(F("loop"));
    display.clearDisplay();
    scroller.update();   // 1フレーム分を描画
    display.display();
    delay(50);           // ~20fps
}


短時間でなかなかよいものができたと思いました。
AI恐るべし。。。

2025/8/30 更新
組み込んで使ってみたら、addしたtextが変更できないことがわかりました。追加しかできない。
それで変更しました。

/*********************************************************************
 *  ScrollText.h
 *********************************************************************/
#pragma once

#include <Adafruit_GFX.h>          // 既存の画面描画ライブラリ
#include <U8g2_for_Adafruit_GFX.h>

//
// ------------------------------------------------------------------
//
class ScrollingText {
public:
    // コンストラクタ: 描画オブジェクトと初期Y座標を受け取る
    ScrollingText(U8G2_FOR_ADAFRUIT_GFX &gfx, int displayW, int y,
                  const uint8_t *font = u8g2_font_unifont_t_japanese1,
                  int speed = -2)
        : gfx(gfx), width(displayW), y(y), font(font), speed(speed) {
        x    = 0;          // 初期位置は任意(ここでは画面右端に設定する)
        minX = 0;          // 以降 setText() で更新
    }

    // テキストをセットすると、minX と初期 X を計算
    void setText(const String &message) {
        msg   = message;
        minX  = -6 * msg.length();     // 6px × 2(フォントサイズ?) = 12px
        x     = width;
    }

    // 1フレーム分更新+描画(呼び出し側で loop() 内に入れる)
    void update(void) {
        if (msg.isEmpty()) return;      // テキストが設定されていなければ何もしない
        // フォント設定
        gfx.setFontMode(1);          // 透明背景
        gfx.setFont(font);

        // 描画位置を設定
        gfx.setCursor(x, y);
        gfx.print(msg);

        // スクロール処理
        x += speed;   // speed は負数で左へ

        // 画面外に完全に出たら右側へ戻す(ループ再表示)
        if (x < minX) {
            x = width;
        }
    }

private:
    U8G2_FOR_ADAFRUIT_GFX &gfx;
    int      width;       // 画面幅(px)
    String msg;          // 現在表示中の文字列
    int   x, y, minX;
    const uint8_t *font;
    int   speed;
};


本体
#include <Adafruit_SSD1306.h>
#include <U8g2_for_Adafruit_GFX.h>
#include "ScrollText.h"

const uint8_t I2C_SDA = 0;   //   for C3mini
const uint8_t I2C_SCL = 1;   //

#define SCREEN_WIDTH 128
//#define SCREEN_HEIGHT 64
#define SCREEN_HEIGHT 32
#define OLED_RESET     -1
#define SCREEN_ADDRESS      (0x3C)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;

ScrollingText scroller1(u8g2_for_adafruit_gfx, SCREEN_WIDTH, 16, u8g2_font_unifont_t_japanese1, -2); // Y=16px
ScrollingText scroller2(u8g2_for_adafruit_gfx, SCREEN_WIDTH, 32-1, u8g2_font_unifont_t_japanese1, -4); // Y=31px

void setup() {
    Serial.begin(115200);

    Wire.begin(I2C_SDA, I2C_SCL);

    if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
        for (;;);
    }
    u8g2_for_adafruit_gfx.begin(display);                 // connect u8g2 procedures to Adafruit GFX

    display.clearDisplay();

    scroller1.setText(F("長い文字列をスクロール表示します。")); // 任意の文字列
    scroller2.setText(F("日本語表示。")); // 任意の文字列
}

void loop() {
    display.clearDisplay();
    scroller1.update();   // 1フレーム分描画&スクロール
    scroller2.update();   // 1フレーム分描画&スクロール
    display.display();
    delay(50);           // 約20fps
}

WebRadio に組み込み
#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <U8g2_for_Adafruit_GFX.h>
#include <WiFiMulti.h>
#include "WiFi.h"
#include "Audio.h"
#include "Rotary.h"
#include "ScrollText.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 smiley[8] PROGMEM = {
  0b00111100,
  0b01000010,
  0b10100101,
  0b10000001,
  0b10100101,
  0b10011001,
  0b01000010,
  0b00111100
};
// 8×8 のビットマップ(例:アンテナアイコン)
const uint8_t antena[8] PROGMEM = {
  0b11111110,
  0b01010100,
  0b00111000,
  0b00010000,
  0b00010000,
  0b00010000,
  0b00010000,
  0b00000000
};
// 8×8 のビットマップ(例:アンテナアイコン)
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);
U8G2_FOR_ADAFRUIT_GFX u8g2_for_adafruit_gfx;
//for scroll text
ScrollingText scroller1(u8g2_for_adafruit_gfx, SCREEN_WIDTH, 32, u8g2_font_unifont_t_japanese1, -2);
ScrollingText scroller2(u8g2_for_adafruit_gfx, SCREEN_WIDTH, 48, u8g2_font_unifont_t_japanese1, -3);

// 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 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;
}

// callbacks
#if 0
void my_audio_info(Audio::msg_t m) {
    Serial.printf("%s: %s\n", m.s, m.msg);
}
#else
// detailed cb output
void my_audio_info(Audio::msg_t m) {
    switch(m.e){
        case Audio::evt_info:           Serial.printf("info: ....... %s\n", m.msg); break;
        case Audio::evt_eof:            Serial.printf("end of file:  %s\n", m.msg); break;
        case Audio::evt_bitrate:        Serial.printf("bitrate: .... %s\n", m.msg); break; // icy-bitrate or bitrate from metadata
        case Audio::evt_icyurl:         Serial.printf("icy URL: .... %s\n", m.msg); break;
        case Audio::evt_id3data:        Serial.printf("ID3 data: ... %s\n", m.msg); break; // id3-data or metadata
        case Audio::evt_lasthost:       Serial.printf("last URL: ... %s\n", m.msg); break;
        case Audio::evt_name:           Serial.printf("station name: %s\n", m.msg);
          scroller1.setText(m.msg);
          break; // station name or icy-name
        case Audio::evt_streamtitle:    Serial.printf("stream title: %s\n", m.msg);
          scroller2.setText(m.msg);
          break;
        case Audio::evt_icylogo:        Serial.printf("icy logo: ... %s\n", m.msg); break;
        case Audio::evt_icydescription: Serial.printf("icy descr: .. %s\n", m.msg); break;
        case Audio::evt_image: for(int i = 0; i < m.vec.size(); i += 2){
                                        Serial.printf("cover image:  segment %02i, pos %07lu, len %05lu\n", i / 2, m.vec[i], m.vec[i + 1]);} break; // APIC
        case Audio::evt_lyrics:         Serial.printf("sync lyrics:  %s\n", m.msg); break;
        case Audio::evt_log   :         Serial.printf("audio_logs:   %s\n", m.msg); break;
        default:                        Serial.printf("message:..... %s\n", m.msg); break;
    }
}
#endif

void setup() {
  Audio::audio_info_callback = my_audio_info; // optional
 
  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(;;);
  }
  u8g2_for_adafruit_gfx.begin(display);                 // connect u8g2 procedures to Adafruit GFX

  display.clearDisplay();

  u8g2_for_adafruit_gfx.setFontDirection(0);            // left to right (this is default)
  u8g2_for_adafruit_gfx.setForegroundColor(WHITE);      // apply Adafruit GFX color
 
  u8g2_for_adafruit_gfx.setFont(u8g2_font_7x13_te);  // icon font
  u8g2_for_adafruit_gfx.setFontMode(1);                 // use u8g2 transparent mode (this is default)
  u8g2_for_adafruit_gfx.setCursor(32,16);                // start writing at this position
  u8g2_for_adafruit_gfx.println(F(" ESP32-S3"));
//  u8g2_for_adafruit_gfx.setFont(u8g2_font_siji_t_6x10);  // icon font
//  u8g2_for_adafruit_gfx.setFontMode(1);                 // use u8g2 transparent mode (this is default)
  u8g2_for_adafruit_gfx.setCursor(0,32);                // start writing at this position
  u8g2_for_adafruit_gfx.println(F("  WebRadio V3.0"));
  u8g2_for_adafruit_gfx.print(F("    2025/8/29"));

  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("xxxxxxxx", "xxxxxxx");  /*Network 1 we want to connect*/
  wifiMulti.addAP("xxxxxxxx", "xxxxxxx");  /*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
  scroller1.setText(F(stations[cur_station].c_str()));

  /* ---------- スクロール設定 ---------- */
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setTextWrap(false);

  // Encoder interrupt
  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), rotaryEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), rotaryEncoder, CHANGE);
}

void loop() {

  static unsigned long lastUpdate = 0;
  const uint32_t UPDATE_MS = 10000;   //
  if (millis() - lastUpdate < UPDATE_MS) {
    ;
  }
  else {
    lastUpdate = millis();
    state = NORMAL;
//    Serial.println(lastUpdate);
  }

  //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 - 2;      //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());
        scroller1.setText(F(stations[cur_station].c_str()));
        scroller2.setText("");
        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 & bitrate
      display.setTextSize(1);
      display.setCursor(0, 8);
      display.print("CH:");
      display.print(cur_station);
      display.print(" ");
      display.print(audio.getCodecname());
      display.print(":");
      display.print(audio.getBitRate(false));
      display.print("bps");
      scroller1.update();   // 1フレーム分を描画
      scroller2.update();   // 1フレーム分を描画

      //display station url
      display.display();
      break;
    case VOLUME:
//      Serial.println("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;
//    default:
//      break;
  }
  vTaskDelay(1);
}


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 Radio2025/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();
}

ここまで


GnuRadio2025/07/20

SDR++でラジオが聴けるようになったので、信号処理のブロックを並べてラジオが作れるというGnuRadioを使ってみました。
GnuRadioのサイト(GNU Radio)ではWindowsは Radioconda をインストールせよとあったので、
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も問題ないです。