Pi Pico 2W Web Radio
A Raspberry Pi Pico 2W has enough power to let you create a very usable web radio when you add a DAC chip and an amplifier.
Listening to music and news on the Internet instead of FM is very much in vogue. In the past, you needed at least a Pi Zero W to get started, but with performance increasing all the time, even microprocessors can now do this, making the web radio smaller, cheaper, and faster. I have already built various web radio projects, from a simple radio with a four-line LC display to a web radio that’s at home in any living room with a Raspberry Pi 4 and a large touchscreen for the stereo system. My current project is downsizing to the minimum: I’m using a Pico 2W, but an ESP32-S3 would just as easily do the trick.
Basic Functions
The radio only supports the most basic functions: It can output MP3 streams from Internet URLs and display the station logo, and it has buttons that let you browse the station list. This frugality keeps the hardware requirements manageable.
There are no limits when it comes to buttons and the screen, although screens with integrated buttons are the obvious choice for the application at hand. You do not need a particularly large screen as you only want the application to display a logo: 135x135 pixels should be fine. Most screens are based on an ST7789 or a similar chip, and there is no shortage of suitable drivers.
To play a radio program, the application continuously downloads an MP3 data stream from a URL and passes the digital data via Inter-IC Sound (I2S) to a digital-to-analog converter (DAC) chip. The DAC resides on an external breakout and determines the audio quality. I’ll be listing a few options for the DAC below.
In addition to a DAC board, you also need browsing buttons and a screen for the logo display. The Pimoroni Pirate Audio HAT combines all the necessary components on a single board (Figure 1). Pimoroni originally developed it for the Pi Zero. In my example, though, I will not be using a Raspberry Pi under the HAT, but a Pico 2W on an adapter board. Figure 2 shows a possible alternative setup with a small Waveshare LCD and a PCM5102A as the DAC, with the Pico 2W under the screen and a mini breadboard in between.


Digital-to-Analog Converter
Depending on your expectations for the web radio, you can choose from a range of DAC boards (Figure 3). The MAX98357A on the left has an integrated Class D amplifier (3W, mono); you can use two of these on the same I2S bus and configure them as a stereo system. The PCM5102A (center) and the UDA1334A (right) have a line-level output and therefore require an external amplifier.

Manufacturers often combine a DAC with an amplifier, which can be quite expensive depending on the features. Many DAC boards can be configured by pins or jumpers, but this is rarely necessary. For products from Asia, it is worth doing a quick Google search, as there is a good chance that another vendor will have instructions for your choice of board.
Connecting to the microcontroller is straightforward. Besides ground and voltage, you need three pins BCLK, WSEL, and DATA for I2S. The first pin defines the clock, the second pin selects the left or right channel, and the third pin supplies the data. On the breakouts you will sometimes find alternative versions of these designations, such as BCK (BCLK), LRCK (“left-right clock”), LCK (WSEL), or DIN (DATA).
Not all pins of the various microcontrollers support I2S. Also, on the Pico 2W, the two pins for BCLK and WSEL must be directly next to each other. The same is true for the Raspberry Pi, where I2S is available on pins GPIO18, GPIO19, and GPIO21. The first two pins are next to each other on the BCM chip, but far apart on the pin strip.
Software
In terms of software, this project uses CircuitPython which, unlike MicroPython, comes with a built-in MP3 decoder, so you can code the actual playback functionality in just a few lines (Listing 1). Lines 1-3 create an MP3 decoder
and an I2S output device (i2s
). Line 4 retrieves a web radio URL (url
) via a GET
request in stream mode. Line 5 then sets the decoder’s input file to the stream’s socket. Finally, Line 6 plays what the decoder delivers.
Listing 1: Playing MP3 Streams
01 buffer = bytearray(bufsize)
02 decoder = audiomp3.MP3Decoder("dummy.mp3", buffer)
03 i2s = audiobusio.I2SOut(pin_bclk, pin_wsel, pin_data)
04 response = requests.get(url, timeout=5, headers={"connection": "close"}, stream=True)
05 decoder.file = response.socket
06 i2s.play(decoder)
Processing the keys is also very simple thanks to the keypad
module that’s built into CircuitPython. The keys
object defined in the second line of Listing 2 monitors the specified GPIOs in the background and generates events that are then processed by the main loop starting in line 5. Line 8 uses the number provided by event.key_number
(an integer between 0
and 3
, depending on which of the four keys was pressed) as an index into the callbacks
list and calls the appropriate processing function (either on_prev
, on_next
, on_reload
, or on_mute
).
Listing 2: Processing Buttons
01 import keypad
02 keys = keypad.Keys([PIN_PREV, PIN_NEXT, PIN_RELOAD, PIN_MUTE], value_when_pressed=False, pull=True, interval=0.1, max_events=4)
03 callbacks = [on_prev, on_next, on_reload, on_mute]
04 [...]
05 while True:
06 event = keys.events.get()
07 if event and event.pressed:
08 callbacks[event.key_number]()
I’ve shown you simplified versions of the code; the real project code for the web radio is somewhat more sophisticated and also includes, for example, controlling the display. You can download the complete CircuitPython web radio program from my GitHub repository.
Results
The web radio mostly works without problems, but not always perfectly. For example, in some cases the radio needs several attempts before playing back a stream. This is due to the MP3 frames with which the decoder first needs to synchronize. Thus far, I have only found one (French) station that did not work at all. In that case, I suspect that the radio station is transmitting the wrong data and causing the decoder to get out of step.
Tests with the Pico W have shown that it works for streams with a constant bitrate (CBR) of 64 bits, above which it starts to stutter. Unfortunately, where I’m based in Germany, there are hardly any broadcasters that still transmit at such a low bit rate, although it would be absolutely fine for many applications. In other words, you will need a Pico 2W to cope with the typical CBR of 128 or 256 bits without any problems. Note that it is important to use unencrypted streams (whose URLs start with http://, not https://) where possible, as decryption really slows down the performance.
A system based on an ESP32-S3 or a microcontroller with similar performance will also work well. As CircuitPython runs on almost any MCU, it should be easy to port the code.
The program presented in this article does not support “I Can Yell” (ICY) metadata: ICY is the name used for metadata tags and responses in the streaming protocol that’s used by the Shoutcast freeware media server; the names all start with ICY. Radio stations mix these info snippets into the actual MP3 data on request. The separation of MP3 frames and ICY metadata would probably overtask the tiny computer that I’ve used, which explains why it is not implemented. Likewise, an additional software mixer for volume control would require too much compute power. However, this is likely to change in the foreseeable future with more powerful MCUs, and my code on GitHub is ready for when this happens.
Conclusions
The implementation of the web radio was not too tricky at the end of the day. Adding a classy look to the project beyond the breadboard stage requires a little more work, of course. If you have some old boxes lying around somewhere, you could upcycle them and install the components inside.
The software is also kept to a minimum and literally invites you to change and expand it. Modern touchscreens can make station selection easier, and a small web server on the Pico 2W would allow remote control via a smartphone. I accept pull requests and am also happy to link from my web radio repository to alternative projects.