I’ve seen plenty Internet Radio examples out there on various platforms, but none on Arduino. Is 2K memory just too little to stream radio effectively? Thought it was time to find out. Turns out it’s no problem at all. Using uIP on ENC28J60 for networking and VS1053 for playback, a stock ATmega328p-based Arduino can stream Internet Radio no problem with plenty RAM to spare.
The example sketch discussed here is something of a “Hello, world.” of Internet Radio. It starts up, connects to a single hard-coded stream, and plays it forever. This makes it simple! Plug and listen. For this example, I’ll use the stream from www.c895.org, “Seattle’s Hottest Music” :)
The easiest way to combine Arduino and ENC28J60-based Ethernet is the Nanode. Alternately, you could use an Arduino Uno with an inexpensive (non-official) EtherShield. I’ve also hooked this up with my own Custom Arduino board with a Custom ENC28J60-based Ethernet board. It’s all the same. The Nanode is the easiest, though.
For the player, I use the Breakout board for VS1053 MP3 and MIDI available from MDFly.com for $25. They are for sale elsewhere on the web too, so anything that breaks out the primary control lines will do the job.
Total off the shelf cost, $65. $40 for a Nanode, $25 for the player module.
EtherBright Library, based on avr-uip
Get the EtherBright library, and put it in your sketchbook libraries. The key to making this project work was finding a small, memory-efficient TCP/IP stack in software that’s mature and well-tested. For the longest time, I thought such a thing did not exist. Then I found uIP, which is part of the Contiki OS. More importantly, I found the avr-uip port which extracts uIP out of Contiki, and pairs it with the ENC28J60 driver. Even with all of its restrictions and compromises, it was able to download files from the web at 180kbytes/sec with only a 500-byte packet buffer. That’s plenty fast enough to keep up with a 128kbits/sec Icecast stream with plenty of time left over for dealing with network hiccups.
Unfortunately, avr-uip is not packaged to work with the Arduino IDE, so I extracted a few key pieces, and repackaged it as the EtherBright library. From avr-uip, it contains:
- TCP/IP stack (‘uip’ directory)
- ENC28J60 drivers (‘drivers/enc28j60’ and ‘drivers/interfaces’)
- Webclient applet. Basic logic for making HTTP requests and parsing the response. Minor changes from the avr-uip project, to use PROGMEM for string storage and to support the ICY protocol.
Get the IcyArduino sketch, and put it in your sketchbook.
Hooking up the hardware is easy, especially with Nanode. The VS1053 unit is attached to the secondary connector outside the digital pins. This table details the setup I’ve used here. It is straightforward to modify it for other EtherShield setups. Do be aware that the Nanode uses pin 8 for SPI chip select, so I’m using pin 10 for the VS1033 chip select. Other shields use pin 10 for the Ethernet, so you’d have to hook up the VS1053 to a different pin and change the software.
Now let’s take a tour through IcyArduino.ino.
First, we initialize serial and printf, then print out some information so we know that all is well sofar.
// Bring up serial and print some hello text
printf_P(PSTR("Free memory = %u bytes\r\n"),SP-(__brkval?(uint16_t)__brkval:(uint16_t)&__heap_start));
Second, we bring up the three layers of uIP, first the chip driver, then the uIP stack, then the webclient applet. Note that this sketch uses static IP addresses for simplicity.
// Bring up ENC28J60 driver
enc28j60Write(ECOCON, 1 & 0x7); //Get a 25MHz signal from enc28j60
// Bring up uIP
// Setup MAC address
// Setup our IP, gateway, and netmask -- all statically
// Bring up the webclient applet
// Now that everything is set up, start periodic timers for uIP
timer_set(&periodic_timer, CLOCK_SECOND / 2);
timer_set(&arp_timer, CLOCK_SECOND * 10);
Finally, once uIP is up, we can finish up by starting the player and connect to our stream.
// Bring up the MP3 player
// Launch a connection to our stream
The loop() is a stock uIP main loop, which is looking for new packets and handing them off to uIP correctly. The one thing I added is some reconnection logic to the arp timer. This fires every 10 seconds, which I thought was a good time to reconnect if we’ve lost the connection. Also, commented out by default, is a troubleshooting mechanism to print uIP statistics. This is a good thing to turn on if things aren’t working right.
// Uncomment to get a periodic dump of stats.
// Also use this timer to reconnect if we've lost connection
This section contains the pre-defined callbacks which the webclient applet will use to signal changes.
The datahandler is called by the webclient applet when there is data to handle. This simply passes it off to the driver to be played, plus captures some statistics about the transfer.
void webclient_datahandler(char *data, u16_t len)
if ( ! started_at )
started_at = millis();
size_received += len;
printf_P(PSTR("%lu: DONE. Received %lu bytes in %lu msec.\r\n"),millis(),size_received,millis()-started_at);
The rest of the file simply deals with the start and stop conditions, setting the current status as appropriate and printing status messages.
connected = true;
uip_log_P(PSTR("TIMEOUT. Reconnecting within 10s.\r\n"));
connected = false;
uip_log_P(PSTR("ABORTED. Reconnecting within 10s.\r\n"));
connected = false;
connected = false;
Nanode For the Win
This project will work fine using a generic Arduino clone of any sort and nearly any ENC28J60-based Ethernet module or shield. But it’s best on Nanode for a few reasons:
- Nanode has a secondary pinout that is much more rational than Arduino. All of the VS1053 wires can plug in neatly and securely into a single 12-pin connector attached to the side of the board.
- Nanode has a its own MAC address. So rather than making up a MAC and hoping it doesn’t conflict with anyone else’s out there, we know we have a proper address.
- Nanode has a LED that doesn’t conflict with SCK, so we can actually use it on a project that relies on SPI. Can’t do that with the stock Arduino LED.