I have something of a weak spot for radio. The regular talk/news radio or run-of-the-mill-pop-music stations don’t strike my fancy, but I very much like stations that specialize in a specific style of music and make it their mission to let their audience experience a broad range of their chosen music style.

The attraction, for me, comes from a combination of the music itself, the surprise effect of regularly hearing new good artists or songs (and, every now and then, positively brilliant artists or songs), and informative talk about the music. Additional icing on the cake may be regular news bulletins from another part of the world, or on the contrary, the sound of a language I don’t understand. And when it comes to dedicated radio stations, the internet has no lack of choices.

My usual way of listening to radio was by connecting my iPad to an amplifier. This worked (and still works) very well, except for this one issue: when the iPad is playing music, it’s wired to the amplifier, which means it’s not available for anything else. Not that this is a fundamental issue of course; there are any number of simple solutions to this problem: bluetooth speakers, a second iPad, a dedicated internet radio, and possibly a bunch more.

However, none of these felt really right. Throwing money at this particular problem of mine seemed like the cheap solution; it feels like removing the issue, but not actually solving it. For many things, removing the problem in this way would be a perfectly fine solution, but in this case the problem felt worthy of more of my attention.

So, in this particular case, instead of heading to the store, I embarked on a project to build my own streaming player, with a double goal:

  1. To have a working player that I can put on a shelf somewhere in the kitchen or living room.
    This implies a bunch of sub-goals: it should look passably good, be powerful enough to fill a room with decent sound, be easy enough to use (no programming skills required!), and ideally be battery powered, so that presence of a wall socket is not a constraint.

  2. To learn a bunch of new things in many areas: WiFi-connected microcontrollers, digital-to-analog signal conversion, audio signals, audio amplification, woodworking, product development, etc.

The photo leading this page is a bit of a spoiler, of course. After roughly one year of development, I had a working player that I was using almost daily, and that already vaguely looked like the player in the photo. After another year, a bunch of refinements found their way into many aspects of the design, I’m still using it daily, and I’m still working on improvements. Along the way, I learned a bunch about electronics, loudness issues, ground loops, KiCad, git, driving LCD screens, and a lot more.

The sections below give a quick overview of the current state of the project. For a more chronological overview, skip to the Parts section at the bottom.


The electronics consist of a Texas Instruments CC3200 launchpad (development board), plugged onto a custom board holding a power section, a DAC, and an amplifier. There is a second circuit board, mounted to the front panel, that holds four buttons (input) and an LCD screen (output).

The CC3200 has an ARM Cortex-M4 running at 80 MHz, and a 2.4 GHz WiFi network stack on board. Broadly, the firmware, running on the Cortex-M4, will make a TCP connection to an Internet radio station, then continuously decode the MP3 stream sent back by the station, and send the samples out to the DAC over SPI. It does this at the sample rate specified by the stream (e.g. 44.1 KHz). As a result, the DAC will then reproduce the audio waveform.

The audio is then sent to a loudness block, which will attenuate the middle frequence range in the signal. This has the effect, relatively speaking, of boosting the low frequencies and high frequencies, leading to better sound at lower volumes.

This “modified” audio signal is then sent through the volume potentiometer, and on to the amplifier. The amplifier is a Class-D amplifier (MAX4295ESE), configured for a 10x gain, to offset the signal reduction in the loudness block. The signal is then fed into a 10cm, 2W loudspeaker.

The power section of the board has a buck converter (LM2596) to convert the 7.2V-9V of the battery pack to 5V, for the DAC and the amplifier. It has a second buck converter (LMR16006Y) to convert the battery pack voltage to 3.3V, for the CC3200 launchpad and the screen.

rev.4 PCB
Power, DAC, amp on 1 PCB
io-board rev.2
I/O board: 4 buttons, 128x64 pixels LCD
rev.3 PCB+launchpad
Earlier version of the PCB (not fully populated),
with the launchpad plugged onto it, and the
first version of the I/O board connected.

The current board is revision 4. This and earlier revisions fixed several issues and added several new items:

  • fix distortion on the SPI data line.
  • amplifier chip: switch from an LM386 to a TBA820M (an old class B amplifier), and then to a MAX4295ESE (class D).
  • add a loudness block in the audio path to make the sound clearer.
  • add a power section so the radio can be fed from an AA battery pack.
  • software-controlled brightness for the LCD backlight (with a PWM pin on the microcontroller), and software-controlled ‘enable’ for the amplifier (GPIO pin).

The audio quality still suffers a bit from interference, which causes the CC3200’s network activity to be audible as clicks in the audio output. With a fully charged battery pack, the clicks are only perceptible if the audio volume is very low; as the batteries drain, it becomes more noticeable. I’m still trying to figure out if this is a ground loop issue, or something else.


The case is made of rubberwood and plywood. The rubberwood is used for the thicker top/left/bottom/right sections, and the plywood is used for the front and back panels. The front and back panels are kept in place by fitting them inside 3mm wide grooves all around the inside surfaces of the rubberwood sections. The top, left and right sections are glued permanently to each other.

The bottom is mounted with screws, so that it can be removed. With the bottom section removed, the front and back panels can be slid out of the grooves, in order to access all the components.

Front view
botton view of case, with all components in place
Bottom view, with the bottom section removed

The front panel actually consists of 2 boards, one that is the actual front panel, and one behind it (front panel backing), that holds the loudspeaker, the I/O board and the on/off-volume-potentiometer-switch in place. You can see this in the picture on the right; the front panels are at the bottom in the picture.

Over a few generations, the case evolved as well:

  • The original front panel was made by hand (using a plunging router), as well as the front panel backing to in which the loudspeaker, screen, etc were mounted. The backing panel was especially rough (since it wasn’t visible…). They were replaced with lasercut panels.
  • The screen and buttons were replaced with larger items, so the front panel and the front panel backing were remade accordingly.

A new case is due, since the screw-holes to fix the bottom section are wearing out. This new case will include a battery holding compartment, since currently the battery pack is loose in the case, and it will have a better backing for the PCB.


The software consists of a few fairly separate blocks:

  • The MP3 decoder
    This is the fairly well-known Helix decoder mp3dec, with some small adaptations. It is built from source as a static library, and linked with the firmware code into a single binary.
  • The input/output and menu system
    The I/O system has a few tasks: it loads the list of known station names and URLs from a file on the Flash file system; it also loads the 3 most recently selected stations from a file on the Flash file system. The menu system shows a list of items, and depending on which item is selected and which button is pressed, a specific action is performed.
    The I/O system also includes a routine to select a WiFi network, and to enter the password for the network.
    The build system includes a mocked device which uses the ncurses library for input and output, so that the input/output subsystem can be built and tested on a regular Linux or Mac system.
  • The player
    This is the part that makes the stream connection, decodes the MP3 frames that come in, and sends the samples to the DAC for playing. The hard part in this component is doing the buffering well, and properly evaluating whether an incoming stream is “stable” and can be played without gaps in the audio.

The firmware source code is here:

The firmware is built into a ~90KB executable. The CC3200 is structured such that, on boot, the firmware is loaded from the Flash file system into SRAM, and then it is started. This means that, of the 256KB of SRAM, there is ~160KB available for run-time data.

Future plans for the software include an RSS parser and associated menu system handler, so podcast subscriptions can be handled, and UPnP functionality so that libraries contained locally on a network server can be played.


Prelude: feasibility
Small, isolated experiments for key project parts: SPI interface to the DAC, producing a tone, decoding MP3.
Revision 1: PCB with MCP4921 and LM386
First board, with the DAC and a small amplifier, and 4 buttons for a rudimentary UI.
Intermezzo: I/O board (v1)
Combine a Nokia 5110 LCD and 4 buttons on a separate UI board.
Revision 2: Enclosure
Made from MDF, and rubberwood recovered from an old door.
Intermezzo: class-D amplifier
Replace the amplifier with a 2W class D one for more efficiency.
Revision 3: integrated PCB
The DAC, a more powerful amplifier, and power supply blocks on a single PCB.
Intermezzo: I/O board (v2)
New version of the I/O board, with a larger screen and larger, softer buttons.
Description of the firmware.
Revision 4: updates and fixes to the rev. 3 board
Power supply rearrangement, new loudness stage, etc.