About Ethernut Hardware Software Projects Appliances Developers FAQ Download Links Imprint Deutsch |
AVR Internet RadioIntroductionThis tutorial presents a step by step guide on how to build an stand-alone Embedded Internet Radio. The device will be attached to a local Ethernet and connects MP3 streaming servers. If an Internet gateway (router) is available, you can listen to public radio stations listed at www.shoutcast.com, for example. Required HardwareEthernut board, preferably version 2 or similar hardware. It's possible to run the code on version 1 boards, but because of the lack of sufficient RAM, the bitrates for MP3 streaming are limited. Medianut board or similar hardware using a VS1001, VS1002 or VS1011 for MP3 decoding. These chips are manufactured by VLSI Solution Oy, Finland,
The Medianut can be mounted on top of the Ethernut board.
Assembled and tested boards as well as components may be purchased from egnite GmbH, Germany, Required SoftwareThe presented code had been tested on Nut/OS 3.9.2.1pre, but should also work on earlier releases of version 3.
Follow the instructions presented in the
Step 1: Connecting an Internet Radio Station
We will utilize an RS232 port for debug messages. Nut/OS provides
a rich set of stdio routines such as printf, scanf etc., which are
fairly similar to Windows and Linux. However, the initialization
is a bit different. There are no predefined devices for stdin, stdout
and stderr. Devices used by an application have to be registered first
by calling
After device registration, we are able to open a C stdio stream
on this device. The function
The
We place an endless loop at the end of #include <dev/debug.h> #include <stdio.h> #include <io.h> #define DBG_DEVICE devDebug0 #define DBG_DEVNAME "uart0" #define DBG_BAUDRATE 115200 int main(void) { NutRegisterDevice(&DBG_DEVICE, 0, 0); freopen(DBG_DEVNAME, "w", stdout); _ioctl(_fileno(stdout), UART_SETSPEED, &baud); puts("Reset me!"); for(;;); } The need for device registration also applies to the Ethernet device. The Ethernut hardware supports two different LAN controller chips, the LAN91C111 and the RTL8019AS. #ifdef ETHERNUT2 #include <dev/lanc111.h> #else #include <dev/nicrtl.h> #endif NutRegisterDevice(&DEV_ETHER, 0x8300, 5); ETHERNUT2 is typically defined in the file UserConf.mk
located in the application directory.
HWDEF += -DETHERNUT2 TCP/IP over Ethernet interfaces typically require some sort of configuration, like the unique MAC address, local IP address, the network mask, routing information etc. The most comfortable way to define these variables is to make use of a local DHCP server. If there is none available in your local network, then you can install a simple one on your Windows PC. DHCP servers are also available on all Linux systems. If you can't or don't want to install a DHCP server, you need to provide the required parameters in your application code defining them as preprocessor macros, for example. #define MY_MAC { 0x00, 0x06, 0x98, 0x10, 0x01, 0x10 } #define MY_IPADDR "192.168.192.100" #define MY_IPMASK "255.255.255.0" #define MY_IPGATE "192.168.192.1"
We use a specific routine to configure the network interface in three trial and error steps. The advantage is, that this code will work with DHCP, hard coded values and, not mentioned yet, previously saved EEPROM values. int ConfigureLan(char *devname) { if (NutDhcpIfConfig(devname, 0, 60000)) { u_char mac[6] = MY_MAC; if (NutDhcpIfConfig(devname, mac, 60000)) { u_long ip_addr = inet_addr(MY_IPADDR); u_long ip_mask = inet_addr(MY_IPMASK); u_long ip_gate = inet_addr(MY_IPGATE); if(NutNetIfConfig(devname, mac, ip_addr, ip_mask)) { return -1; } if(ip_gate) { if(NutIpRouteAdd(0, 0, ip_gate, &DEV_ETHER) == 0) { return -1; } } } } return 0; } NutDhcpIfConfig() assumes, that
a complete configuration is available in the ATmega EEPROM already.
This may be the case, if you previously ran the Basemon application
and entered these parameters before starting the sample Webserver.
If the fuse "Preserve EEPROM Contents" has been enabled in the
ATmega128, then the network configuration remains intact when
erasing the device during the programming cycle, while uploading a
new application.
This first call will fail, if the EEPROM is empty. A second call
is done, which provides the MAC address. Again, this will fail,
if DHCP is not available in the local network. The final call
to After having configured the network interface, we can use it to establish a TCP/IP connection to an MP3 streaming server. Three items are required to specify an MP3 stream.
[playlist] numberofentries=2 File1=http://64.236.34.196:80/stream/1020 Title1=(#1 - 90/29131) Smoothjazz.Com Length1=-1 File2=http://64.236.34.4:80/stream/1020 Title2=(#2 - 124/38816) Smoothjazz.Com Length2=-1 Version=2 File1 (alternative File2 ) entry contains
the information we need to create the proper parameters in our
radio application.
#define RADIO_IPADDR "64.236.34.196" #define RADIO_PORT 80 #define RADIO_URL "/stream/1020" TCPSOCKET *sock; u_long ip = inet_addr(RADIO_IPADDR); sock = NutTcpCreateSocket(); NutTcpConnect(sock, ip, RADIO_PORT); stream = _fdopen((int) sock, "r+b"); fprintf(stream, "GET %s HTTP/1.0\r\n", RADIO_URL); fprintf(stream, "Host: %s\r\n", inet_ntoa(ip)); fprintf(stream, "User-Agent: Ethernut\r\n"); fprintf(stream, "Accept: */*\r\n"); fprintf(stream, "Icy-MetaData: 1\r\n"); fprintf(stream, "Connection: close\r\n\r\n"); fflush(stream); line = malloc(512); while(fgets(line, 512, stream)) { cp = strchr(line, '\r'); if(cp == 0) continue; *cp = 0; if(*line == 0) break; printf("%s\n", line); } free(line);
mnut01-041111.zip Medianut Tutorial Part 1 - Nut/OS 3.9.2.1 pre - AVRGCC 29877 bytes free Configure eth0...OK MAC : 00-06-98-21-02-B0 IP : 192.168.192.202 Mask: 255.255.255.0 Gate: 192.168.192.1 Connecting 64.236.34.196:80...OK GET /stream/1040 HTTP/1.0 ICY 200 OK icy-notice1: <BR>This stream requires <aef="http://www.winamp.com/">Winamp</a><BR> icy-notice2: SHOUTcast Distributed Network Audio Server/SolarisSparc v1.9.4<BR> icy-name: CLUB 977 The 80s Channel (HIGH BANDWIDTH) icy-genre: 80s Pop Rock icy-url: http://www.club977.com icy-pub: 1 icy-metaint: 8192 icy-br: 128 icy-irc: #shoutcast icy-icq: 0 icy-aim: N/A Reset me! Step 2: Playing an MP3 StreamFollowing the empty line, which marks the end of the header, the streaming server will send an endless stream of binary data, the MP3 encoded audio data. Reading this data into a buffer is nothing special. int got; u_char *buf; buf = malloc(2048); got = fread(buf, 1, 2048, stream); Nut/OS includes a device driver for the VS1001K decoder chip. Actually this is not a common device driver with a NUTDEVICE structure and support for stdio read and write. This wouldn't make much sense, because tiny systems like Ethernut suffer from buffer copying. The following code would result in low performance. /* Bad example */ got = fread(buf, 1, 2048, stream); fwrite(buf, 1, got, decoder); /* Not available */
NutSegBufInit(8192); NutSegBufReset(); for(;;) { buf = NutSegBufWriteRequest(&rbytes); got = fread(buf, 1, rbytes, stream); NutSegBufWriteCommit(got); }
With this scheme, data copying is reduced by 25% and takes place
As stated above, the VS1001 driver doesn't support stdio read and write routines. Instead a number of individual routines are provided to control the decoding process.
It is possible to access the segmented buffer from within interrupt
routines and the Nut/OS VS1001 driver makes use of this feature.
However, calling u_char ief; ief = VsPlayerInterrupts(0); /* Exclusive call here. */ VsPlayerInterrupts(ief);
mnut02-041111.zip Medianut Tutorial Part 2 - Nut/OS 3.9.2.1 pre - AVRGCC 29743 bytes free Configure eth0...OK MAC : 00-06-98-21-02-B0 IP : 192.168.192.202 Mask: 255.255.255.0 Gate: 192.168.192.1 Connecting 64.236.34.196:80...OK GET /stream/1020 HTTP/1.0 ICY 200 OK icy-notice1: <BR>This stream requires <a href="http://www.winamp.com/">Winamp</a><BR> icy-notice2: SHOUTcast Distributed Network Audio Server/SolarisSparc v1.9.4<BR> icy-name: Smoothjazz.Com - The worlds best Smooth Jazz - Live From Monterey Bay icy-genre: smooth jazz icy-url: http://www.smoothjazz.com icy-pub: 1 icy-metaint: 8192 icy-br: 32 icy-irc: #shoutcast icy-icq: 0 icy-aim: N/A Read 594 of 16384 Read 512 of 15790 Read 512 of 15278 Read 512 of 14766 Read 512 of 14254 Read 512 of 13742 Read 512 of 13230 Read 512 of 12718 Read 512 of 12206 Read 144 of 11694 4834 buffered Step 3: Refining the Player
The default setup of the Nut/Net TCP stack is optimized for tiny
embedded systems with data exchange in both directions at minimal
memory usage. We can use
u_short tcpbufsiz = 8760; NutTcpSetSockOpt(sock, SO_RCVBUF, &tcpbufsiz, sizeof(tcpbufsiz));
u_short mss = 1460; NutTcpSetSockOpt(sock, TCP_MAXSEG, &mss, sizeof(mss));
Another problem appears, when the server or the connection dies.
In such a case our MP3 player, the TCP client, may never return
from the u_long rx_to = 3000; NutTcpSetSockOpt(sock, SO_RCVTIMEO, &rx_to, sizeof(rx_to)); As we found out in the last step, audio output contains hiccups or may even become completely scrambled. The reason is, that the stream contains some additional information, the so called metadata tags. In the previous step we passed this unfiltered to the decoder chip, which is of course quite picky about extra bytes included in the MP3 stream. The server sends an information about how many bytes of MP3 data are between the metadata tags in the initial header lines. icy-metaint: 8192 The metadata tag begins with a single byte, which indicates the total size of the tag when multiplied by 16. Here's an example of the contents of such a metadata tag. StreamTitle='Alphaville, Big in Japan';StreamUrl='http://www.club977.com/ads'; StreamTitle typically informs us about
the music title currently transmitted. For now we print this to
our debug device.
mnut03-041111.zip Quite often we will something like this after starting the player. Connecting 64.236.34.196:80...Error: Connect failed with 10060 Reset me!
|