Arduino on Ice: Internet Radio via Shoutcast

Nanode streaming Internet Radio (close-up)

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” 🙂

Hardware

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.

Software

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.

VS1053 Library

Get the VS1053 library. For the player, I just followed the code in this thread on the VLSI forum, and cleaned it up a bit into a nice library.

IcyArduino Sketch

Get the IcyArduino sketch, and put it in your sketchbook.

Connections

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.

Nanode Wire VS1053
5V brown 5V
0V red GND
13 SCK green SCLK
12 MISO orange SO
11 MOSI yellow SI
10 SS blue XCS
INT
3V3
7 MAC white XDCS
6 LED
5 purple XRESET
4 grey DREQ

IcyArduino explained

Now let’s take a tour through IcyArduino.ino.

setup

First, we initialize serial and printf, then print out some information so we know that all is well sofar.

void setup(void)
{
  // Bring up serial and print some hello text
  Serial.begin(57600);
  printf_begin();
  printf_P(PSTR(__FILE__ "\r\n"));
  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
  network_prepare_MAC(mac.addr);
  network_init();
  enc28j60Write(ECOCON, 1 & 0x7); //Get a 25MHz signal from enc28j60
  
  // Bring up uIP
  uip_init();
  
  // Setup MAC address
  uip_setethaddr(mac);
  
  // Setup our IP, gateway, and netmask -- all statically
  uip_ipaddr_t ipaddr;
  uip_ipaddr(ipaddr, 192,168,1,55);
  uip_sethostaddr(ipaddr);
  uip_ipaddr(ipaddr, 192,168,1,1);
  uip_setdraddr(ipaddr);
  uip_ipaddr(ipaddr, 255,255,255,0);
  uip_setnetmask(ipaddr);
  
  // Bring up the webclient applet
  webclient_init();
  
  // 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
  player.begin();
  
  // Launch a connection to our stream
  connect();
}

loop

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.

    if(timer_expired(&arp_timer))
    {
      timer_reset(&arp_timer);
      uip_arp_timer();
    
      // Uncomment to get a periodic dump of stats.
      //dump_uip_stats();
    
      // Also use this timer to reconnect if we've lost connection
      if (!connected)
        connect();
    }

callbacks

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)
{
  Serial.print('.');
 
  if ( ! started_at )
    started_at = millis();
 
  size_received += len;
 
  player.playChunk(reinterpret_cast<uint8_t*>(data),len);
 
  if (!data)
  {
    Serial.println();
    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.

void webclient_connected(void)
{
  uip_log_P(PSTR("webclient_connected"));
  player.startSong();
  connected = true;
}
  
void webclient_timedout(void)
{
  uip_log_P(PSTR("TIMEOUT.  Reconnecting within 10s.\r\n"));
  player.stopSong();
  connected = false;
}
  
void webclient_aborted(void)
{
  uip_log_P(PSTR("ABORTED.  Reconnecting within 10s.\r\n"));
  player.stopSong();
  connected = false;
}
  
void webclient_closed(void)
{
  uip_log_P(PSTR("webclient_closed\r\n"));
  player.stopSong();
  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.

Some cool things about Nanode

20 Comments

Filed under Arduino, Audio, Ethernet

20 responses to “Arduino on Ice: Internet Radio via Shoutcast

  1. ah, so this is what you were using avr-uip for :)

    Cool. When I have some extra time i it looks like it would be fun to build one of these.

    • Yup, and I wish I didn’t have to repackage avr-uip to work in the Arduino IDE. Have a look at NanodeUIP sometime, this is another avr port of UIP that does work in the Arduino IDE. The original author has a nice take on the ‘port mapper’ problem. My fork is here: https://github.com/maniacbug/NanodeUIP

      • ah, so this is what you were using avr-uip for :)

        So, I had that exact same idea about the function pointers two nights ago. 🙂 I just have’t had time to switch the code over.

        So, what did you have to do to repack the code for arduino?

  2. Quentin Arce

    Hmm, So, it looks like you moved all of the code to a single level and then added a header with the extern C defs since the code for arduino is in C++. Is that the sum of what was required?
    I would like to understand why the arduino dev env required the flat dir structure. :-\ I haven’t really played with it yet. Thanks for your help.

    • Yup, Arduino requires a mostly flat structure. I also gave some thought to what exactly is library code versus app code. For example, uip has lots of app specific code in apps/Webserver-stokerbotng, but httpd.c is really lib code.

      Also important, the libs cannot be compiled with app-specific headers. So per app config is basically impossible.

      The lib header file just has to be there. It could be empty. The IDE looks for headers in the sketch to decide which libs to link in.

  3. arnulf

    Thanks to maniacbug for sharing all this code with us!

    When compiling icy_radio with Arduino 1.0 on Nano 3.0:
    VS1053.cpp: In member function ‘uint16_t VS1053::read_register(uint8_t) const’:
    VS1053.cpp:137: error: ‘SPI’ was not declared in this scope

    Should SPI be defined in the VS1053 class ?
    I’m lost here 😉

    • Hi, it looks like something is going wrong in your environment. Can you turn on verbose compiling, grab the entire output, drop it into pastebin, and paste the link here? Then we can see what’s going on under the hood.

      Also, please make sure the SPI examples compile for you.

  4. arnulf

    NanodeUIP\webserver compiles & works fine 🙂
    Here errors for NanodeUIP\icy_radio:
    http://pastebin.com/g2UsLZp4

    • arnulf

      Receved my card yesterday and compilled IcyArduino (this works !)
      Using Arduino Nano connected VS1053 as described in code:
      VS1053 player(/* cs_pin */ 9,/* dcs_pin */ 6,/* dreq_pin */ 7,/* reset_pin */ 8);

      It seems it connects and download data, but no sound. If I disconnect pin 8 (reset)
      and connect again, I get distorted sound, isn’t the Nano capable to stream 128 kbps ?
      or is my VS1053 card broken ?, program output in pastebin below:
      http://pastebin.com/QDpR3mQe

      • Hey, you got a good start 🙂  I’ve found VS1053 modules to be pretty finicky. Which module did you get?

        A first thing to do would be to just get some sound working first.  Try just recording a very short 3-second clip, convert it to a low bitrate, compile that in, and try to get it to play.

  5. arnulf

    No luck so far 😦 on the bright side, RF24 works ! 🙂
    I ordered the same card as on your picture, from here: http://www.electrodragon.com/?product=vs1053-mp3-decoding-module
    but, I got LCSOFT STUDIO VS1003/1053, picture: http://www.siliconray.com/catalog/product/gallery/id/576/image/1462/
    (I’m not able to read what chip is used)

    Did you have any problem getting the card to play ?

  6. kmmankad

    Hey,
    great project! I have a small request.. Could you share a wireshark dump of the streaming? I just want to see what it looks like.
    Have you tried doing the other way? A shoutcast server i.e.(Arduino+ENC28J60+SD Card with mp3 files)
    Regards,
    kmmankad

  7. Hector

    maniacbug, this is an amazing tutorial and great opportunity for anyone that wants to try and do some nice cost effective experiments with internet radio.

    I’ve been trying to get my own project off the ground and I had to settle with getting an Arduino Ethernet Uno and using the VS1053 Breakout Board from MDFly. I’ve tried to connect all the cables and see if I can get any activity, but I seem to be stuck in even trying to make any connection to the router. Not sure how I am caught at this stage, I have been changing the ip address to much larger numbers to avoid any conflicts and the MAC address to match what is printed on the Ethernet Arduino, but no matter these changes it seems that it can’t get past the “connecting…” portion.

    Any help would be awesome! This is for a project to upload and broadcast updating memory recording for an installation.

    Thanks again for the amazing tutorial.

    • Hi, thanks I’m glad you found it useful. The official Arduino Ethernet board uses a different Ethernet chip than used in this post, so you won’t find this library useful at all for that. The regular Arduino networking libs should do the job for you. Good luck with your project!

      • Hector

        Hi!

        I ended up purchasing a Nanode 5 from Wicked Devices (http://wickeddevice.com/index.php?main_page=product_info&cPath=26&products_id=109) and have in my possession a VS1053 decoder. I’ve gone and mimicked what you had there to the smallest detail (I can provide images for proof) and rewired and changed everything and regardless what I do I can not connect online. I’ve done the DHCP test on the Nanode itself and it was able to get an IP address on its own. I’ve also changed the IP address from 192.168.1.55 -> 192.168.1.121 to make sure it was out of range in time.

        I’ve even gone ahead and began to rewire the 4 specific cables of (SO, xCS, SCLK, SI) and there still isn’t any change. Interesting, when I had the SCLK wire from the VS1053 decoder plugged into the SCK port on the Nanode, that’s the only time I received any confirmation that the Nanode was connecting to the VS1053 player (even though the instructions never say to do that.) After that it just never connects. It ends up being stuck on this:

        /tmp/build46789.tmp/IcyArduino.cpp
        Free memory = 715 bytes

        I’m not sure if the libraries provided here no longer work for the Nanode 5 as oppose to this older model?

        Very stuck and would appreciate any direction!

  8. Nokia GSM

    Hi,

    Is their any chance you can paste this sketch with the official Arduino Ethernet board.

    Thanks in Advance

  9. dotcommandante

    has someone tried to get RF24 Library working with the VS1053 Library – Should be some sort of Audio distribution system?

Leave a comment