University of California at Berkeley

College of Engineering

Department of Electrical Engineering and Computer Science

 

EECS194

Spring 2008

Lab 2

D. E. Culler

J. Hui & P. Dutta

 

Important: Please use the lab worksheet to hand in answers to questions electronically. Take-home exercises in the lab worksheet are due at the next class.

 

 

Section 1: Embedded Network Programming

 

 

Setup

 

Grab the source files for lab 2 and put them in your VM as follows:

            cd $HOME/eecs194

     wget http://inst.eecs.berkeley.edu/~cs194-5/sp08/lab2/src/lab2.tar.gz

     tar xzvf lab2.tar.gz

 

 

OS Networking APIs – Implementing Clients and Servers

 

Before we get our hands dirty with programming embedded nodes, we’ll familiarize ourselves with how we program networked applications on a typical operating system. We intended to do this at the end of Lab 1, but ran out of time. If you went all the way through Lab 1, this will be a good review for you and provide you with a bit more technical depth in traditional OS networking.

 

In a UNIX environment, the networking API is primarily BSD sockets (yes, the ‘B’ means it was invented right here at Berkeley). Sockets allow messages to be sent and received between two end-points (a.k.a. sockets). Sockets can be used for more than just inter-networking using TCP/UDP/IP; they are also used to communicate with various system-level components such as I/O and device drivers. For the purposes of this class, we are mostly interested in the inter-networking functionality that sockets provide

 

 

Exploring UDP Sockets

 

We will begin by looking at a simple UDP client and server. Let’s first run some examples on Linux.

 

First open up a terminal and compile the Linux examples as follows:

            cd apps/linux

     make

 

Now start the UDP server example.

            ./udpserver 1234

 

Open up a different terminal and run the client example.

            ./udpclient ::1 1234

 

-          What does the client do?

-          What does the server do?

-          What is the equivalent of '::1' in IPv4?

-          What does 1234 correspond to?

 

 

UDP Client

 

Okay, now that you have a feel of what these programs do, let’s open them up and see how these are implemented. Using your favorite editor, open up udpclient.c.

 

The main() function starts by parsing command-line arguments and setting up a signal handler to clean up resources when interrupted. It’s generally a good idea to have these when building a robust application.

 

Now to the real stuff. The next action in main() is to setup the server variable, which is declared as a sockaddr_in6. You can find the definition of sockaddr_in6 in /usr/include/netinet/in.h. The actual code for initializing server is shown here:

server.sin6_family = AF_INET6;

if (inet_pton(AF_INET6, argv[1], &server.sin6_addr) <= 0)

  error("Invalid IPv6 address\n");

server.sin6_port = htons(port);

 

-          What does AF_INET6 specify?

-          What does the port specify?

-          What does htons() do?

-          What does inet_pton() do?

 

Note: If you don’t know what something in UNIX does, chances are there is a man page about it. Man is the UNIX way of documenting things. For example, you can execute the following at the command-line:

            man memcpy

 

After setting up the server variable, a new socket is creating using the socket command.

     sockfd = socket(AF_INET6, SOCK_DGRAM, 0);

 

Look up the man-page for socket to find out more.

 

Now everything is setup properly to start using the socket. The client app now just sits in a while-loop sending messages to the server using sendto and receives messages using recvfrom. Lookup the man pages to find out more about them.

 

 

UDP Server

 

Let’s take a look at the server code. Using your favorite editor, open up udpserver.c. Notice that much of the code actually looks the same. The server still initializes the server variable, creates a socket, sends and receives messages. However there are subtle but important differences. Let’s take a look at each one.

 

Notice that the server variable is initialized slightly differently.

 

-          What is the IPv6 address set to? What does it mean? (hint: in.h may be useful to you again)

-          Why is specifying a port important?

 

There is also a new function call being used: bind

 

-          What does bind do?

 

The remainder of the programming is pretty much the same as the client, except that recvfrom and sendto are reversed.

 

 

If you haven’t figured out already, the control flow is as follows:

 

 

Sockets API in Other Settings

 

Since its original implementation, BSD Sockets have been ported to many languages other than C. Included in the Linux directory is a python example of the UDP client: udpclient.py. Python is known for its ease of quickly programming new applications. Take a look at the python UDP client and compare it to the C version. You’ll quickly notice why Python has been gaining lots of popularity in recent years.

 

Additionally, the original BSD sockets API is tightly tied to the execution model defined by the OS, in this case UNIX. The BSD sockets API is written with sequential programming in mind and that is why all of the examples you’ve seen so far contain while-loops in the main body of the program. This will be different when we get to the embedded code.

 

 

Interacting with 6LoWPAN

 

So far, we’ve only been playing with clients and servers on the same Linux machine. This communication model is already fairly powerful as it lets you communicate with other process on the same box, while the kernel provides all of the nice buffer management and queuing needed to get messages from one process to another.

 

 

But now let’s use the UDP client application to interact with 6LoWPAN nodes. As some of you may recall from the last lab, the 6LoWPAN nodes are running an Echo server.

-          If you don’t already have your 6LoWPAN network running, set it up as you did in Lab 1.

-          If you reprogrammed your nodes in the last lab, you will need to install UdpEcho on them.

cd apps/Echo

make telosb

swupdate –t build/telosb/tos_image.xml i <ipaddr>

swupdate r <ipaddr>

 

Now try running udpclient again with <ip6addr> set to the IPv6 address of one of your sensor nodes. Pick a node that still has the original application loaded from when we handed them out last week.

            ./udpclient <ip6addr> 7

 

Not much difference there, right?

 

 

Programming TinyOS

 

Okay, enough with the Linux side for now. Let’s move on to the 6LoWPAN side. As a part of this exercise, we’ll also get a feel for how to program in the TinyOS component framework using the nesC programming language.

 

 

TinyOS Modules and UDP Sockets

 

Let’s go take a look at the 6LoWPAN UDP server in apps/Echo/.

 

Now open up EchoUdpP.nc. This file contains the module for the UDP server. A TinyOS module provides and/or uses a set of interfaces and implements the code for functions specified within the interfaces.

 

  module EchoUdpP {

    uses interface Boot;

    uses interface Udp;

  }

  implements {

    … implementation here …

  }

 

At first glance, things look pretty different than they did on the Linux side. There’s no main, no need to create a socket, and now it looks like we’ve implemented a function called recvfrom() rather than actually using one. It’s a small snippet of code, but it exemplifies many of the TinyOS goals and design philosophies.

 

First, notice that all logic within this module is done only within event handlers. The booted event occurs when the underlying OS is ready to go. This is equivalent to main() in a traditional program (you could argue that main() is also an event handler). It’s the first event that gets signaled. In the booted event, there’s a call that should look familiar to you: bind.

 

  event void Boot.booted() {

    call Udp.bind( ECHO_PORT );

  }

 

The second event, recvfrom(), is signaled whenever a UDP message is received by the socket. Because this is the Echo server, all it needs to do is send the message right back using sendto(). Both of these calls should look familiar to you. Don’t worry about minor differences in the arguments for now.

 

  event void Udp.recvfrom( void *buf, uint16_t len,

                           sockaddr_in6_t *from,

                           link_metadata_t *linkmsg ) {

    call Udp.sendto( buf, len, from );

  }

 

 

TinyOS Interfaces

 

You’ll also find that most of this code looks like basic C-code with a few added key words. The nesC language is just that. The event keyword signifies call flow coming up through an interface. The call keyword signifies call flow going down into the interface. The keywords indicate that the functions belong to an interface being used or provided by the component. Interface functions are specified in nesC interface files. The UDP interface is specified in:

 

            interfaces/Udp.nc

 

The keywords also act as hints to the nesC compiler so that it can verify that all interface functions are implemented. Any module using the UDP interface must implement the recvfrom() handler and is free to call any of the commands, such as bind().

 

To see what interfaces the kernel provides, open up src/BinKernel.nc. Notice that it has both Boot and UDP. It has a lot of other interfaces too. Another interesting note is the Timer interface, which is indexed by the parameter id. This is called a parameterized interface and simply represents multiple instances of the Timer interface. We will deal with timers in the next example.

 

As a side note, the nesC compiler takes nesC code and transforms it to C-code, which is then passed to a C-compiler, such as GCC. To view the generated C-code, first compile it:

 

            make telosb

 

Now look in build/telosb/app.c. Don’t expect it to look pretty!

 

 

TinyOS Configurations

 

Now take a look at EchoUdpC.nc. This file contains a configuration for the UDP server. This is where the power of modularity takes place in TinyOS. Configuration files simply ‘wire’ modules together. In other words, when a module uses an interface, the configuration file specifies which specific implementation to use. In this case, both Boot and UDP are implemented by the kernel.

 

-          Why don’t we need to create a socket as we did with the socket call in Linux?

 

Wiring is a perfect example of TinyOS’ design goal philosophy of static binding. In a traditional OS, calling the sockets command allocates some state (specifically, a file descriptor). When you have very limited memory and resources, dynamic allocation can make it hard to be predictable. Another example of dynamic allocation is malloc. You will rarely see malloc in TinyOS. Instead, all variables, buffers, etc. must be declared explicitly within the module and allocated at compile time.

 

-          Why does static binding and allocation make the system more predictable?

-          What capabilities do you lose if you do not allow dynamic allocation/binding?

 

 

TinyOS Sensor and Actuator Drivers

 

Okay, let’s move to a slightly more interesting application. Go to the CountEvents application. Open up CountEventsP.nc and look at what it does.

 

-          What does CountEvents  do?

 

Now open CountEventsC.nc and see what components are pulled in. There’s a few more things here than in our Echo application.

-          Timers: Note the use of the new keyword. Recall that when we were looking at KernelC.nc the Timer interface was parameterized. In doing so, the Kernel can provide multiple instances of the Timer service. The new keyword creates a new instance of TimerMilliC(), which wires to one of the many instances of Timer. You can see the implementation in src/TimerMilliC.nc.

-          Actuation: Actuators are electrical devices that can cause physical actions. In this example, we are actuating LEDs (Light Emitting Diodes).

-          Sensors: Sensors take physical inputs and turn them into electrical information that can be processed. In this example, we are using a button as a sensor.

 

Now change DST_ADDR to point to the IPv6 address of your LoWPAN interface on the Linux box. Then compile and wirelessly install the CountEvents application to your node. If the red LED is blinking, then the application is running.

 

-          How can you view, in real-time, the number of messages being sent? (we want more than 3-bits of information)

-          How do you change the state of the blue LED?

 

 

General-Purpose I/O

 

Okay, let’s actually see how we implement device drivers on the node. Information flows in and out of the MCU through its pins. The pins can provide very simple information such as a simple high or low signal or something more rich such as a peripheral bus or even generate analog signals using a Digital to Analog Converter (DAC). Pins can also act as inputs for digital signals, busses, and analog signals through an Analog to Digital Converter (ADC) as well.

 

Open up drivers/LedsP.nc and take a look at how it drives the LEDs. Note the usage of P5DIR and P5OUT. These are control registers for specific pins. Nearly all of the hardware peripherals on the MSP430 are accessed through memory-mapped registers. The use of memory-mapped registers makes it very convenient, as you can just read and write to bits at a specific memory location. You can find the definitions for P5DIR and P5OUT in:

/opt/msp430/msp430/include/msp430/gpio.h

 

To see what P5DIR and P5OUT actually do, take a look at the TI MSP430 datasheet:

            http://www.ti.com/litv/pdf/slau049f

 

-          What do the registers P5DIR, P5OUT, and P5IN do?

-          How would you control the green LED? (hint: see the telosb schematic at: http://webs.cs.berkeley.edu/tos/hardware/telos/telos-revb-2004-09-27.pdf)

-          What if you want to make the pin an input, what do you do?

 

You should now know how to make pins output, set whether they are logic high or low, set them to input, and read whether they are high or low.

 

 

Interrupts

 

If we set a pin to input, how do we detect that it has changed state?

-          We could always set a timer and read it periodically, this is called polling.

-          Another option is wait for an interrupt

 

-          What are the advantages of waiting for an interrupt?

-          How would you poll an input and still allow the processor to sleep most of the time?

 

Now take a look at drivers/ButtonP.nc. This is how we setup and handle an interrupt for the user button on the telosb mote.

-          The HplSignal interface captures the hardware event.

-          Note the use of the async keyword in front of the event. This indicates that the event will be called as a direct result from a hardware event and can preempt any currently running application event. In order to cross from a hardware event to application event, we must post a task. Doing so will allow the kernel to execute the task when it is safe and not while in the middle of other application events.

 

-          In what cases can an async event preempting an application event be problematic?

-          Are all pins capable of generating interrupts? See the MSP430 datasheet to find out.

 

 

Writing Your Own Driver

 

While the application is called CountEvents, we never specified what kind of events it is actually counting. In this section, we will modify the code such that we count real physical events.

 

Modify the sensor driver to do the following:

 

-          Count the number of times the sensor triggers.

-          Signal an event to the application that indicates when the button was pressed and how many times it has been pressed since the boot. (hint: you will want to modify the Button interface)

-          In the application, report this information to the Linux VM in ASCII form so that you can actually see how many times the button was pressed.

 

Note that you can write applications that cause the entire OS to fail and won’t even be able to be remotely upgradable. To get out of this state, you can physically reprogram your nodes as follows:

<remove your bridge node from USB and replace it with a sensor node>

            sudo /opt/rosetta/init.d/lowpan stop

     make telosb install

     sudo /opt/rosetta/init.d/lowpan start

            <remove sensor node from USB and insert bridge node>

 

-          Why is it so easy to hang TinyOS and the application?

-          Give one or two examples that will hang a TinyOS system.

 

 

Processing Sensor Input

 

Let’s start doing some real processing on the sensor input. Modify the sensor driver again to do the following:

 

-          Measure the time difference between button pushes.

-          Periodically, report the min, mean, and max time between button pushes to the Linux VM.

 

 

Now you’re ready to write some cool apps!

 

 


Section 2: Simple circuits for binary inputs and outputs.

 

Sensor Networks tie together several distinct, but related worlds:

  • The physical world of space, things, mechanical actions, positions, forces, heat, motion, gravity, acceleration, light, chemical concentrations, magnetism, radio waves, and so on.
  • The analog world of electronics, with voltage, current, resistance, capacitance, and inductance.
  • The digital world of Boolean values (true and false), logic, bits, and numbers.
  • The information world where we give meaning to logical and numerical values, process and manipulate them, visualize information, draw conclusions, and elect to take actions.

We move between these worlds often in every day life; we flip switches and push buttons to turn on lights and gadgets, we read meters and say “its hot”, we turn dials to select particular radio stations, we track packages on the web, we run spreadsheets over all sorts of data, and so on.  There are literally thousands of different sensors are available for all sorts of different phenomena using all sorts of different sensing mechanisms and many different interfaces.  Here we will explore some extremely simple ones.

 

 

Binary Devices

 

We begin with the simplest classes of devices – those that operate in the binary domain of On/Off, Yes/No, True/False, and Black/White – no shades of gray.  A huge class of interesting input and output devices fall into this class and are easily integrated into your embedded wireless web.

 

Switches – analog binary inputs

 

Probably the simplest “sensor” in every day life is the switch.    Physically, the switch is a mechanical device that has an orientation – up/down, in/out – that we think of an On/Off.  Electrically, it an analog device with two configurations – open and closed.  Viewed from a slightly different perspective, it is a position sensor that has infinite resistance (open circuit) in one position and zero resistance (closed circuit) in the other.  Indeed, many switches are used to detect the physical presence of an object that moves the switch – in addition to the more conventional uses of switches to turn electrical devices on and off.

 

 

Figure 1. Simple Circuit Converting an Analog Switch into a Binary Digital Signal

 

The translation from the analog switch (open/closed) to a digital value (true/false) is performed by a very simple circuit, shown in Figure 1, on the Extender.  In essence, this is a 1-bit analog-to-digital converter (ADC).  When the switch is open, the capacitor charges until it reaches the supply voltage, Vacc.  At this point, the voltage at D represents a logical 1 or TRUE value.  When the switch is closed it creates a path to ground allowing current to flow.  The low voltage at D represents a logical 0 or FALSE value. This digital input can be connected directly to a microcontroller through General Purpose IO (GIO) pin, where it can be processed and acted upon.  This simple circuit is also a low-pass filter that debounces the switch, so even if the opening and closing of the switch contacts is not perfect, the microcontroller sees a discrete change in the Boolean value. 

 

Although we translated the mechanical switch into a digital input, some analog aspects remain – especially power.  When the switch is closed, current flows and power is consumed.

 

Suppose that we want to run for 2 years on two AA alkaline batteries and we want the switch support circuitry to consume only 10% of that energy budget.

-          Find the energy capacity of the batteries in mAh (milliamp-hours)

-          Assuming the switch is essentially always closed, determine the allowable current for the switch.

-          Since you know the voltage of the battery and Ohm’s law, determine the size resistor that should be used for the pull-up.

 

For this R, diagram the rise and fall time of the input as a function of C.

 

Wire up an example in the lab using parts provided, including a magnetic reed sensor for the switch.  Capture the behavior on an oscilloscope in the lab.

 

 

Trip Sensors

 

We usually think of switches as the mechanical devices that control lights and appliances, but switch-based binary sensors are extremely common.  A few examples are illustrated in Figure 4.  Many irrigation equipment providers offer rain gauges that open when a configured water level is obtained.  Similar devices are used for tank levels, floats and the like. Mechanical vibration and tilt sensors open when a slight tilt causes an internal metal ball to roll off one of the contacts.  The magnetic reed contact sensors used widely in home and building security are magnetically controlled switches.  When the magnet attached to the window or door is adjacent to the sensor, the switch is closed; it opens when the magnets are separated.  Miniature magnet reed switches are often used on rotating shafts so that a pulse is generated whenever the magnet passes by; such techniques are used for tachometers, flow sensors, weather veins, and many other applications.  Pressure, water flow and current trip-sensors cause a circuit to be formed or broken when a certain threshold is obtained.

Figure 2 Example Switch-based Sensors

 

 

Study the telosb schematic and determine how you would connect your debounced magnetic reed sensor to this platform?

-          to use interrupts to detect transitions?

-          to use polling to detect the state of the switch?

 

 

Relays and Set Points - Binary outputs

 

The IO ports can also be configured as outputs to control and actuate external devices.  The classical use of this is a relay, in which an electronic device causes a analog switch to open and close.  Of course, binary outputs are also used to light LEDs and perform other functions.

 

One analog precaution that must be observed is the current drawn by the external device.  No device should draw more than 6 mA from a pin and the total current drawn from all microcontroller outputs combined should not exceed 48 mA.  This is particularly an issue when the external device is analog, such as an LED.  The effective resistance should be large enough that the current draw through the output pin is within tolerance.  For binary devices, current draw is seldom a problem, but attention must be paid to voltage level.  The TI MSP microcontroller operates at CMOS levels, below 3.3 volts.  You will notice that the LEDS in telosb are in series with a resistor to moderate the current.  Even so, at 5 mA, an LED draws as much as the whole MCU!

 

-          Assuming that the resistance of the LED is negligible, calculate the size of resistor that will keep the current under 5 mA.

-          The larger the current the brighter the LED.  In fact, your eye will perceive a rapidly blinking (20 Hz) bright LED as much brighter than one solidly on at the same overall energy consumption.

-          If the LED is to consume no more than 10% of the 2 year budget, what is the total time per day that it can be on?