logo elektroda
logo elektroda
X
logo elektroda

Internet radio and audio file player on ESP32-S3

MAJSTER XXL 293088 2808

TL;DR

  • Built an ESP32-S3 internet radio with an audio file player and OLED desk display, evolving through v1, v2, and v3.
  • Uses an ESP32-S3-WROOM-1, PCM5102A DAC, rotary encoders, and GitHub-hosted station lists split into banks of up to 100 stations.
  • The prototype uses a 2.42" 128x64px white OLED display, with a PCB made in Poland costing 130zl.
  • v3 adds a colour screen, weather, a calendar, RSS news from Polsat News Polska, and full remote control.
  • The first PCB had a pad-spacing mistake for the ESP32-S3-WROOM-1, so the module pins had to be bent slightly to fit.
AI summary based on the discussion. May contain errors.
ADVERTISEMENT
Treść została przetłumaczona polish » english Zobacz oryginalną wersję tematu
📢 Listen (AI voice):
  • Question about the source of the cassette animation code

    #2791 21942222
    DJCheester
    Level 28  
    Posts: 2089
    Help: 75
    Rate: 958
    It’s a cool animation – I’m all for it – but this design for a tapeless tape recorder is a travesty of retro kit, a bit like fitting Chinese radios into the cabinets of old wooden valve radio sets. Real gear should be preserved, not modified like that – that’s my view. When I was younger, I once converted a ZK147 reel-to-reel tape recorder by fitting electronics from a Technics deck into it. After replacing the head with a new one (the tape recorder was a four-track model), I was able to turn the mono ZK147 into a stereo unit. It sounded better than the original – the dynamics were improved and, on top of that, it gained Dolby. Admittedly, it wasn’t quite the same anymore, but the soul of that kit still lay in the tape playback. I posted this ‘invention’ on Elektroda and got a bit of flak for that kind of remake. Just a little off-topic.

    Getting back to the cassette and the project – it isn’t open-source (I’m talking about this tape recorder); ejcona, did you write the cassette animation code yourself?

    Best regards...
  • ADVERTISEMENT
  • #2792 21942797
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    >>21938600
    Metal audio front panel with screen and two knobs on a workshop bench
    This is how it looks at the moment – I still need to change the glass to tinted glass


  • ADVERTISEMENT
  • #2793 21942812
    robgold
    Level 23  
    Posts: 726
    Help: 10
    Rate: 290
    >>21942797 I’d go for bigger knobs. Although, for me, I think I’ll always associate this layout with Safari 5, which is why I’m so keen on sticking with a single encoder all the time ;)
  • #2794 21942876
    DJCheester
    Level 28  
    Posts: 2089
    Help: 75
    Rate: 958
    >>21942797

    Well, that’s a lovely little gadget – I think I’ll sit down with my PCB and make one myself, especially as the weather isn’t exactly brilliant.

    Congratulations and best regards...
  • #2795 21943681
    DJCheester
    Level 28  
    Posts: 2089
    Help: 75
    Rate: 958
    >>21942797

    Ejcon, have you shared this project anywhere on GitHub (I’m talking about the one with 4xESP and 3xLCD from the last video)?

    Best regards....
  • #2796 21943684
    ArturAVS
    Moderator
    Posts: 26382
    Help: 2301
    Rate: 7819
    @ejcon, change the labels on the analysers. Not 250k, but 250Hz. :D
  • #2797 21943689
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    >>21943684
    Thanks, I didn’t realise – I suppose I need to have a bit of a rest if I’m making mistakes like this

    Added after 13 [minutes]:

    >>21943681
    No, I haven’t shared anything
  • ADVERTISEMENT
  • #2798 21943704
    ArturAVS
    Moderator
    Posts: 26382
    Help: 2301
    Rate: 7819
    Internet radio and audio file player on ESP32-S3

    Not that I’m criticising your work in any way. You’ve put a lot of effort into it, and a ‘mistake’ like this does spoil the effect, unfortunately. Why do you want to change the front to a tinted/smoked finish? How did you cut out that ‘piece of glass’?
  • ADVERTISEMENT
  • #2799 21943709
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    >>21943704
    This is milled polycarbonate with a film applied to it
  • #2800 21943714
    ArturAVS
    Moderator
    Posts: 26382
    Help: 2301
    Rate: 7819
    If it weren’t for those jagged outer edges… It would be brilliant! I’d also change the font on the cassette to one that’s 2–4 pixels larger and set it to Bold. That would improve the readability.
  • Suggesting tinted glass and rear-mounted panel window

    #2801 21943809
    efi222
    Level 22  
    Posts: 779
    Help: 12
    Rate: 1218
    If the ‘window’ in the front panel is larger than the displays, the purpose of the tinted glass is to conceal the interior of the case. In such cases, I also use additional black masking with cut-outs for the active area of the display.
    In the photo of the panel, light is reflected off the glass. However, from a different angle, the contents of the case will be visible.

    Unless, of course, this is intentional :)

    @ejcon, how thick is the front panel? If it’s reasonably thin, might it be easier to fit a piece of glass larger than the window behind the cut-out?
  • #2802 21943827
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    Bottom view of a green PCB with a connected multicolored wire bundle inside a metal enclosure
    Screenshot of a 3D model of a rectangular panel with several rectangular cutouts
    >>21943809
    The panels will be printed on a printer, but I need to send the files to a mate as I haven’t bought a printer yet.
  • Display recess modified with 0.4 mm frame cover

    #2803 21943855
    efi222
    Level 22  
    Posts: 779
    Help: 12
    Rate: 1218
    I would suggest modifying these recesses to accommodate the displays. An additional 0.4 mm thick frame
    The deeper recess accommodates the entire display. An additional thin cover conceals the display outside the active part of the screen.
    It works well for me.

    Internet radio and audio file player on ESP32-S3

    Internet radio and audio file player on ESP32-S3

    Internet radio and audio file player on ESP32-S3

    Black rectangular plastic bezel with an inserted display on a light background
  • #2804 21943863
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    >>21943855
    There are some faults at the bottom – what you saw was the front in the photo
  • #2805 21943934
    efi222
    Level 22  
    Posts: 779
    Help: 12
    Rate: 1218
    @ejcon. Also, the direction in which the tape moves inside the cassette is the wrong way round.
    In other words, the movement is fine, it’s just that the spools are spinning the wrong way round.
  • #2806 21944145
    DJCheester
    Level 28  
    Posts: 2089
    Help: 75
    Rate: 958
    >>21943863

    Are you planning to share the code?
    I’d love to see the whole thing, but what I’d find most useful is the code for this cassette. It looks brilliant. Best regards...
  • Cartridge code licensing and speed reduction

    #2807 21944156
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    The cartridge code – so these codes will be a compiled version for the ‘Elektroda’ group; those who’ve earned it will get it for free, whilst others, unfortunately, won’t. For you, it’ll be the full version with a licence; after installation and sending certain parameters, you’ll need to enter the licence details. It might not be fair, but I didn’t go to all that trouble just for someone to sell my work.

    Added after 7 [minutes]:

    >>21943934
    Already corrected and speed reduced.
  • Cassette UI drawing helpers for ESP32-S3

    #2808 21945261
    ejcon
    Level 17  
    Posts: 300
    Help: 3
    Rate: 68
    CASSETTE CODE
    // ============================================================================
    // MODE 16 - ANIMOWANA KASETA MAGNETOFONOWA
    // ============================================================================
    static void v50DrawCassetteReel(int cx, int cy, int tapeRadius, float angle)
    {
      gfx.tftFillCircle(cx, cy, tapeRadius + 3, 0x2104);
      gfx.tftFillCircle(cx, cy, 25, 0xEF7D);
      gfx.tftFillCircle(cx, cy, 15, 0xBDF7);
      for (uint8_t tooth = 0; tooth < 8; ++tooth)
      {
        float a = angle + tooth * 0.7853982f;
        int x1 = cx + lroundf(cosf(a) * 9.0f);
        int y1 = cy + lroundf(sinf(a) * 9.0f);
        int x2 = cx + lroundf(cosf(a) * 22.0f);
        int y2 = cy + lroundf(sinf(a) * 22.0f);
        gfx.tftDrawLine(x1, y1, x2, y2, 0x4208);
        gfx.tftFillCircle(x2, y2, 2, 0x4208);
      }
      gfx.tftFillCircle(cx, cy, 6, 0x1082);
      gfx.tftFillCircle(cx, cy, 3, 0xFFFF);
    }
    
    static void v50DrawCassetteLevel(int x, int y, int pct, const char *label)
    {
      gfx.tftText(x, y - 1, label, 1, 0xFFFF);
      pct = constrain(pct, 0, 100);
      for (uint8_t i = 0; i < 14; ++i)
      {
        int threshold = (i + 1) * 100 / 14;
        uint16_t on = i < 9 ? 0x07E0 : (i < 12 ? 0xFFE0 : 0xF800);
        gfx.tftFillRect(x + 12 + i * 9, y, 7, 7, threshold <= pct ? on : 0x2945);
      }
    }
    
    // Czytelniejszy tekst na etykiecie kasety: pogrubienie 2 x 2 piksele.
    static void v50DrawBoldText(int x, int y, const char *text, uint8_t size, uint16_t color)
    {
      if (text == nullptr || text[0] == '\0') return;
      gfx.tftText(x,     y,     text, size, color);
      gfx.tftText(x + 1, y,     text, size, color);
      gfx.tftText(x,     y + 1, text, size, color);
      gfx.tftText(x + 1, y + 1, text, size, color);
    }
    
    void v50PanelCassette()
    {
      static float reelAngle = 0.0f;
      static float tapeTransfer = 0.0f;
      static int8_t tapeDirection = 1;
      static uint32_t lastCassetteMoveMs = 0;
      static uint32_t playSeconds = 0;
      static uint32_t lastSecondMs = 0;
      static float meterL = 0.0f, meterR = 0.0f;
      static float meterVelL = 0.0f, meterVelR = 0.0f;
      static float previousAudioL = 0.0f, previousAudioR = 0.0f;
      uint32_t now = millis();
      bool playing = !volumeMute && SampleRate > 0;
    
      if (playing && now - lastCassetteMoveMs >= 60UL)
      {
        lastCassetteMoveMs = now;
        reelAngle -= 0.08f; // dodatni kąt = obrót przeciwnie z ruchem wskazówek
        if (reelAngle < 0.0f) reelAngle += 6.2831853f;
        tapeTransfer += tapeDirection * 0.00045f;
        if (tapeTransfer >= 1.0f) { tapeTransfer = 1.0f; tapeDirection = -1; }
        if (tapeTransfer <= 0.0f) { tapeTransfer = 0.0f; tapeDirection = 1; }
      }
      if (playing && now - lastSecondMs >= 1000UL)
      {
        lastSecondMs = now;
        playSeconds = (playSeconds + 1) % 6000UL;
      }
    
      gfx.tftFillScreen(0x0000);
      // Czerwono-czarna obudowa inspirowana klasyczną kasetą 90 min.
      gfx.tftFillRoundRect(5, 5, 310, 230, 12, 0xA800);
      gfx.tftDrawRoundRect(7, 7, 306, 226, 10, 0xFBAE);
      gfx.tftFillRoundRect(12, 10, 296, 221, 8, 0x1082);
      gfx.tftFillRect(16, 62, 288, 101, 0xB800);
    
      // Biała etykieta: stała czcionka Unicode i przewijanie po pełnych znakach UTF-8.
      // Font nie zmienia się w trakcie przewijania, a polskie litery są czytelne.
      gfx.tftFillRoundRect(18, 10, 284, 50, 4, 0xFFFF);
      gfx.tftDrawFastHLine(24, 34, 272, 0xE38E);
      String radioLine = tftScrollWindowUtf8(tftStationName(), 34, 520);
      String songLine = tftScrollWindowUtf8(tftNowPlaying(), 34, 360);
      gfx.tftTextUtf8(25, 14, radioLine.c_str(), 1, 0x0000);
      gfx.tftTextUtf8(25, 37, songLine.c_str(), 1, 0x0000);
    
      // Przydymione okno kasety oraz zmieniająca się ilość taśmy na rolkach.
      gfx.tftFillRoundRect(25, 70, 270, 85, 8, 0x0841);
      gfx.tftDrawRoundRect(27, 72, 266, 81, 7, 0x7BEF);
      int leftTapeRadius = 37 - lroundf(tapeTransfer * 13.0f);
      int rightTapeRadius = 24 + lroundf(tapeTransfer * 13.0f);
      v50DrawCassetteReel(92, 111, leftTapeRadius, reelAngle);
      v50DrawCassetteReel(228, 111, rightTapeRadius, reelAngle);
      gfx.tftDrawLine(92, 139, 228, 139, 0x8A22);
      gfx.tftDrawLine(92, 140, 228, 140, 0xC326);
    
      // Środkowa szybka z przesuwającą się taśmą.
      gfx.tftFillRoundRect(126, 83, 68, 55, 4, 0x2104);
      gfx.tftDrawRoundRect(128, 85, 64, 51, 3, 0x8410);
      for (uint8_t i = 0; i < 7; ++i)
      {
        int px = 132 + ((i * 13 + (int)(reelAngle * 9.0f)) % 56);
        gfx.tftFillRect(px, 108, 5, 3, 0xFDC8);
      }
    
      // Wyższy czerwony pasek i większa czcionka. Usunięcie podwójnych spacji
      // pozwala zmieścić cały napis w szerokości 272 px przy rozmiarze 2.
      gfx.tftFillRoundRect(24, 154, 272, 32, 3, 0xE920);
      gfx.tftText(22, 160, "BASF90 EVO4 MULTI STEREO", 2, 0xFFFF);
    
      float rawL = playing ? (float)tftVuPct(displayVuL) : 0.0f;
      float rawR = playing ? (float)tftVuPct(displayVuR) : 0.0f;
      float targetL = v50MusicBeatTarget(rawL, previousAudioL, 0.66f);
      float targetR = v50MusicBeatTarget(rawR, previousAudioR, 0.66f);
      meterVelL = (meterVelL + (targetL - meterL) * 0.64f) * 0.67f;
      meterVelR = (meterVelR + (targetR - meterR) * 0.64f) * 0.67f;
      meterL = constrain(meterL + meterVelL, 0.0f, 100.0f);
      meterR = constrain(meterR + meterVelR, 0.0f, 100.0f);
      int levelL = lroundf(meterL);
      int levelR = lroundf(meterR);
      v50DrawCassetteLevel(25, 190, levelL, "L");
      v50DrawCassetteLevel(25, 203, levelR, "R");
    
      char counter[8];
      snprintf(counter, sizeof(counter), "%02lu:%02lu",
               (unsigned long)(playSeconds / 60UL), (unsigned long)(playSeconds % 60UL));
      gfx.tftText(250, 190, playing ? "PLAY" : "STOP", 1, playing ? 0x07FF : 0xF800);
      gfx.tftText(250, 204, counter, 1, 0xFFE0);
      gfx.tftText(278, 219, "M16", 1, 0xFFFF);
    }
    As I mentioned, I’m sharing the cassette code.
  • #2809 21945311
    DJCheester
    Level 28  
    Posts: 2089
    Help: 75
    Rate: 958
    Thanks, I’ll have a think about it when I’ve a spare moment.

    Best regards...
📢 Listen (AI voice):

Topic summary

✨ Discussion of a DIY internet radio and SD-card audio player built on an ESP32-S3 with an OLED display, rotary encoders, buttons, and a front-panel interface. The project uses Wi‑Fi to download station lists from GitHub/public stream databases, supports MP3 and later AAC/FLAC playback, displays metadata including Polish characters via a modified font table, and stores settings such as station/bank selection and volume. The thread covers PCB design in EasyEDA, ESP32-S3 module pin spacing issues, PSRAM and Arduino IDE setup, library compatibility problems, SPI/I2S wiring, SD card reader boot issues, encoder behavior, WiFiManager-based network setup, and troubleshooting of crashes, HTTP download failures, and audio initialization errors. Several improvements were added over time, including station selection by encoder, time sync, browser update support, and saving settings to card memory.
AI summary based on the discussion. May contain errors.
ADVERTISEMENT