Adding network support to a DSP-based embedded system
Drew is a wireless-communications/DSP systems engineer and president of Elintrix where he is currently engaged in the development of networked radio and sensor products. He can be contacted at [email protected]. Anthony is cofounder and chief engineer of software development at Elintrix and author of Embedded Software Development with eCos. He can be contacted at [email protected].
On one hand, adding networking support to embedded devices can be a daunting task, primarily because network stacks are so resource intensive. On the other hand, going to the trouble of adding networking support makes standardized access to devices possible because these protocols are portable across many platforms. TCP/IP, for instance, makes it possible for a wireless sensor node to communicate using the native protocol of most networking infrastructures, where each node in the sensor network can be accessed by a desktop PC, Tablet PC, PDA, or even cellphones.
Networking support also improves the basic feature set each node is capable of supporting. For example, if an alarm condition occurs in the network, the device with the fault can generate e-mail and send it off to instantly notify the network operator about the problem. Another benefit is that web-based management can be easily incorporated into all devices with network support. Technicians can then connect directly to a sensor node for configuration and monitoring using a standard PDA equipped with a standard web browser. In effect, the sensor node's web server and HTML pages are the new user interface for the device.
There are a number of available network stacks that target deeply embedded systems where processor cycles and memory are limited. In this article, we take an in-depth look at one of thesethe uIP network stackand then port it to a DSP-based wireless sensor board.
Figure 1, for instance, is a sensor network made up of nodes that incorporate a network stack and a web server to enable web-based management. Individual network nodes can then be controlled and monitored through HTML pages sent by the nodes' web server, as in Figure 2. The File Transfer Protocol (FTP) can be used in a networked sensor node to update existing software for feature upgrades or bug fixes. Dynamic sensor node configuration information can also be sent to a particular sensor node.
Networking & Wireless Sensor Nodes
One of the keys in deciding which stack best fits your device is determining the resource requirements for the software. The amount of data each node in your system needs to transmit and receive during communication sequences should also dictate which solution is right for your design. For example, if your sensor node wakes up every hour to transmit a few bytes of data, a compact network stack implementation is ideal. On the other hand, systems that are constantly transmitting large packets of data might need a stack implementation that offers better packet buffer management.
It is important when evaluating and selecting a lightweight network stack implementation that it allows communication with standard, full-scale TCP/IP devices. An implementation that is specialized for a particular device and network might cause problems by limiting the ability to extend the nodes' network capabilities in other generic networks. It is always possible to implement your own network stack, and this might be necessary depending on your particular device's needs. However, with the assortment of solutions available in the open-source community, leveraging existing technology lets you quickly move development ahead.
The following software network stacks are open-source solutions for smaller sensor nodes that must take into account resource usage. This list is not comprehensive, but rather a starting point for further investigation:
- lwIP. The "lightweight IP" stack is a simplified but full-scale TCP/IP implementation. lwIP is designed to be run in a multithreaded system with applications executing in concurrent threads; however, it can also be implemented on a system with no real-time operating system (RTOS). The protocols include Internet Control Message Protocol (ICMP), Dynamic Host Configuration Protocol (DHCP), Address Resolution Protocol (ARP), and User Datagram Protocol (UDP). It provides support for multiple local network interfaces and is flexible enough to let it be easily used on a wide variety of devices and scaled to fit different resource requirements.
- OpenTCP. Tailored to 8- and 16-bit microcontrollers, OpenTCP incorporates the ICMP, DHCP, BOOTP (Bootstrap Protocol), ARP, and UDP protocols. There are also several applications included with OpenTCP, such as a Trivial File Transfer Protocol (TFTP) server, a Post Office Protocol Version 3 (POP3) client to retrieve e-mail, Simple Mail Transfer Protocol (SMTP) support to send e-mail, and a Hypertext Transfer Protocol (HTTP) server for web-based access.
- TinyTCP. TinyTCP is designed to be modular and only include the software required by the system. For example, different protocols can be compiled in/out based on configuration. It provides a BSD-compatible socket library and includes the ARP, ICMP, UDP, DHCP, BOOTP, and Internet Group Management Protocol (IGMP) protocols.
- uC/IP. Pronounced "mew-kip," uC/IP is designed for microcontrollers and based on BSD. Intended to be used with the uC/OS RTOS. Protocol support includes ICMP and Point-to-Point Protocol (PPP).
- uIP. The "micro IP" stack is designed to incorporate only the absolute minimal set of components necessary for a full TCP/IP stack. There is support for only a single network interface. Application examples included with uIP are SMTP for sending e-mail, telnet server/client, an HTTP server and web client, and Domain Name System (DNS) resolution.
In addition, some RTOSs include or have network stacks ported to them. One such example is eCos, an open-source RTOS that includes both the OpenBSD and FreeBSD network stacks as well as application layer support with an embedded web server (for more information, see Embedded Software Development with eCos, by Anthony Massa, Prentice Hall PTR, 2003).
If a network stack is included or already exists in your device, several embedded web servers are available to incorporate web-based control. One such open-source solution is the GoAhead WebServer (see "Integrating the GoAhead WebServer & eCos," by Anthony Massa, DDJ, November 2002).
There are also resources that detail a ground-up approach to developing a small TCP/IP stack intended for embedded systems (TCP/IP Lean: Web Servers for Embedded Systems, by Jeremy Bentham, CMP Books, 2002).
The uIP Network Stack
The uIP (pronounced "micro IP") stack was designed specifically for resource-constrained embedded devices. Nevertheless, it is a full TCP/IP stack implementation, and its size and resource requirements make it ideal for applications such as wireless sensor nodes. Any processor that contains a reasonable amount of internal memory can support the entire network stack on board, eliminating the added expense of external RAM; see Figure 3.
uIP utilizes a single global buffer, which has a configurable size based on the maximum packet length supported, for incoming and outgoing packets. Because of this, the application must process the incoming packet before the next packet arrives, or the application can copy the data into its own buffer for processing later. Incoming data is not stored into the packet buffer until after the application has processed the previous data.
The standard socket interface is the BSD socket API. This interface relies on an underlying multitasking operating system to function properly. Due to this requirement and needed overhead, uIP does not support the socket interface. Instead uIP uses events. Therefore, data arriving on a connection (or an incoming connection request) causes an event to be sent to the application for processing.
To avoid buffering outgoing data before it has been acknowledged, uIP requires the application to assist in packet retransmissions. When the uIP stack determines that a retransmission must occur, an event is sent to the application. The application reproduces the data previously sent. Sending data the first time and during a retransmission is the same from the application's perspective; therefore, the same code can be used. The complexity for determining when the retransmission must take place is contained in the uIP stack itself, eliminating this from the application.
To examine the resource requirements for the uIP stack implementation, the Atmel AVR processor architecture and the GNU compiler Version 3.3 were used to perform measurements. Table 1 gives resource usage for a uIP configuration with one listening TCP port, 10 TCP connection slots, 10 ARP table entries, a packet buffer of 400 bytes, and the simple HTTP server.
uIP includes a Perl script for converting web pages into static memory arrays so they can be incorporated in the compiled code. This eliminates the need for any type of filesystem when using the web server.
The total RAM usage is dependent on various configurable components within uIP. These components are easily set up in the uIP options include file. Things you can configure include the number of TCP connections supported to save RAM and the amount of memory allocated for the receive/transmit buffer, especially if packet sizes are typically smaller on the network.
Certain code modules can be eliminated altogether if you're using a serial network interface. Serial protocols are available, such as the Serial Line Interface Protocol (SLIP) or the more robust PPP protocol. Opting for a serial interface instead of Ethernet allows ARP support code and the ARP table to be removed. Not using UDP can also reduce the code size. These options are easily removed, via macros, in the uIP stack.
The flow of incoming packets can be throttled by adjusting the maximum segment size of incoming data allowed by the sensor node. Other system optimizations that are ideal for wireless sensor networks are available, but require more in-depth configuration. Areas for further investigation include TCP/IP header compression techniques to reduce overhead of the various protocol headers. This is best for sensor systems comprised of nodes that typically transmit smaller data packets.
For TCP, packet retransmission schemes are available that move the resending of lost packets closer to the destination of the data rather than relying on the source point. This can also reduce the length that an acknowledgement needs to travel through the system.
Porting the uIP Stack to the TMS320C5509 DSP
To further examine the uIP stack, we built a prototype wireless radio sensor board that was based on the TMS320C5509 digital-signal processor from Texas Instruments. For most ports of the uIP stack, the TCP/IP code doesn't need to be modified. However, unique features of the TMS320C5509 DSP made it necessary for us to change uIP core files. Figure 4 illustrates the uIP directory structure. A new architecture-specific directory, with files copied from the UNIX directory, can be created for the new port.
The radio sensor board does not have a typical Ethernet port used with network stacks; this is an add-on module that is currently not present on the main board. This system uses a software-based serial port for network connectivity. Some host configuration is necessary in order to set up the host PC to communicate using SLIP over the serial port.
The uIP stack includes an example SLIP driver, which we modified to accommodate the radio sensor board hardware. The original SLIP driver polls the serial port for incoming data and stores each character, applying the SLIP data translation changes where necessary, as it arrives. The sensor board code uses an interrupt for serial port, allowing the ISR to store the incoming data. When a complete network packet is received, a flag is set to start the processing of the data.
One of the major differences between the C5509 and most processors is the treatment of the char type. On the C5509, a char is 16 bits rather than 8 bits, as found on typical processors. This adds some challenges to porting the uIP code that is designed to accommodate an 8-bit char type. In the C5509 world, a char, short, and int are all at 16 bits and data space memory is addressed 16 bits at a time.
As data comes in to the serial driver, it is stored into a buffer. The data in the buffer in a typical system (with an 8-bit char type) would look similar to Figure 5(a), while Figure 5(b) shows the buffer in the C5509 system. When a structure is overlaid on this data buffer for parsing, the members of the structure do not contain the correct data values. Because of this difference, the main structure for the TCP/IP header, uip_tcpip_hdr (located in the uip.h file), must be modified. Furthermore, the code that uses this structure must also be modified.
Listing One(a) is the original uip_tcpip_hdr header structure. Structure members that are of type u8_t do not need to be modified, because the only difference on the C5509 platform is that these types will occupy 16 bits of memory each instead of the typical 8 bits. However, the u16_t type members do need to be changed. Listing One(b) is the modified structure to accommodate the C5509-based prototype board.
Next, these structure changes are incorporated into the uIP networking code. An example of code using the new structure is in uip.c, where the TCP source and destination port numbers are swapped to format the header of an outgoing packet. Listing Two(a) is the original code, and Listing Two(b) the modified code. Other variables that pose the same issue between the C5509 platform and a standard processor are also modified in a similar way. In this example, BUF is a uip_tcpip_hdr pointer type and points to the start of the incoming network packet. Because we changed the original srcport from a 16-bit unsigned short into an array of two 16-bit unsigned short elements, we need to store the srcport array elements properly. This example also shows the destport code modification.
These modifications are done throughout the uIP source code. Another file that needs attention is uip_arch.c, which is located in the architecture-specific directory. This file contains the TCP and IP checksum routines.
After porting the uIP networking code to the C5509, the main processing loop is implemented. Listing Three shows a slightly modified version of the processing loop included in the uIP source code.
The initialization is performed for all of the necessary drivers and uIP modules including the serial port (serial_init), timer (timer_init), and SLIP drivers (slipdev_init), as well as the uIP stack (uip_init), and the HTTP web server (httpd_init).
The slipdev_poll function checks to see if the serial driver has received a packet. If a packet has arrived, the uip_len is set to the packet length. The data is then processed by calling uip_input, which is a macro that calls the main uIP processing routine uip_process for handling the received packet.
If a data packet has not been received, a timer count is checked to see if it is time to call the periodic function (aptly named uip_periodic). The timer counter, ulTimer0Count, is incremented every millisecond in the timer interrupt service routine. The periodic routine also calls the main processing routine, uip_process, to maintain any open connections in the system.
There are a few things to keep in mind during the porting process:
- If a timer is not available, a variable can be used to count when a second has occurred and, therefore, when to call the periodic function. However, the code will have to be calibrated based on processor clock, and any modifications to the code affect this timing.
- It might be helpful to enable debugging in the uIP stack to get output from the code. Setting the macro UIP_LOGGING to 1 in the file uipopt.h located in the architecture-specific directory can do this.
- Also included with the web-server code is a Perl script that generates C code from HTML files. This can be found under the httpd\fs directory.
- Various uIP stack parameters, such as the number of simultaneous network connections and packet buffer size, can be customized in the file uipopt.h.
Conclusion
Embedding a networking stack is no longer an arduous task that requires an enormous amount of resources. As we've shown, there are a number of solutions that use the TCP/IP protocol suite for communication. Tailoring one of the network stack solutions to the specific characteristics of the sensor node device ensures the system will operate at its most optimum level and utilize system resources best. We would like to thank Adam Dunkels, developer of the uIP stack, for his support during the porting process.
DDJ
typedef struct { /* IP header. */ u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; u16_t ipchksum; u16_t srcipaddr[2], destipaddr[2]; /* TCP header. */ u16_t srcport, destport; u8_t seqno[4], ackno[4], tcpoffset, flags, wnd[2]; u16_t tcpchksum; u8_t urgp[2]; u8_t optdata[4]; } uip_tcpip_hdr;(b)
typedef struct { /* IP header. */ u8_t vhl, tos, len[2], ipid[2], ipoffset[2], ttl, proto; u16_t ipchksum[2]; u16_t srcipaddr[4], destipaddr[4]; /* TCP header. */ u16_t srcport[2], destport[2]; u8_t seqno[4], ackno[4], tcpoffset, flags, wnd[2]; u16_t tcpchksum[2]; u8_t urgp[2]; u8_t optdata[4]; } uip_tcpip_hdr;Back to article
Listing Two (a)
/* Swap port numbers. */ tmp16 = BUF->srcport; BUF->srcport = BUF->destport; BUF->destport = tmp16;(b)
/* Swap port numbers. */ tmp16 = ((BUF->srcport[1] << 8) | BUF->srcport[0]); BUF->srcport[0] = BUF->destport[0]; BUF->srcport[1] = BUF->destport[1]; BUF->destport[0] = (tmp16 & 0x00FF); BUF->destport[1] = ((tmp16 >> 8) & 0x00FF);Back to article
Listing Three
// Initialize and start the uIP timer to fire every 1 ms. timer_init(); // Initialize the serial port for use with the uIP stack. serial_init(); // uIP networking stack related initialization. slipdev_init(); uip_init(); httpd_init(); // Main processing loop. while (1) { // If we get a length greater than zero, we know a packet is ready for // processing. Otherwise, we call the periodic function once per second. uip_len = slipdev_poll(); if (uip_len == 0) { // Call the periodic function once per second and loop through all // connections. if (ulTimer0Count >= 1000) { ulTimer0Count = 0; for (uiConn = 0; uiConn < UIP_CONNS; uiConn++) { uip_periodic( uiConn ); // If the periodic function resulted in data that needs to // to be sent out, length is set to a value greater than zero. if (uip_len > 0) slipdev_send(); } } } else { // Process the incoming data. uip_input(); // If the input function resulted in data that needs to // to be sent out, the length is set to a value greater than zero. if (uip_len > 0) slipdev_send(); } } // End of main loop.Back to article