Wireless Embedded Internetworking Short Course
David E. Culler
Department of Electrical Engineering and Computer Science
|
Lab 6 Reporting |
|
In lab 5 we built an UDP/IP based embedded network application in which the infrastructure essentially polled the embedded device by sending requests to it. In this lab we are going to develop a very different network architecture. In many embedded network settings the embedded device samples periodically are reports its readings to a controller, log, analysis, or other infrastructure resource. Alternatively, it may send alarm when some condition is detected. Or it may be a combination of the two. In lab 2 we implemented periodic sample, regular actuation, and alarm inputs. Let’s extend those ideas into a network form.
If you feel comfortable with the UDP request/response server that you built in Lab 5, feel free to run with that. If instead you would like a little head start with this lab, we have built a partial solution in $TOS/rreport. The client peer to this is udpreporter.c in either IPv4 or IPv6. This version is very simple as it discards any message sent by the client, but uses its IP address for the following reports. Notice that the classic internet tools, like telnet and nc (and your web browser) don’t quite work for this because they expect request/response. Here there is a configuration request followed by a stream of reports.
Our example also illustrates some important kernel interfaces, in particular the GlobalTime interface. The localtime method is really uptime since the mote booted. The globaltime is an embedded version of what NTP provides. Because correlated time is so important in embedded system there have been much research into time synchronization. This one provides synchronized time as an option that is piggybacked on the IPv6 router advertisements. The value is unix time – the number of seconds elapsed since midnight Coordinated Universal Time (UTC) of January 1, 1970, not counting leap seconds. This only works if the router has NTP.
We hope that you will start to run with your own ideas in this lab, but here’s some thoughts to get you started.
In addition to transforming your application into a rich reporting service, let’s slip a much more complete and robust driver infrastructure underneath of it. This will give us an opportunity to introduce you to one of the powerful composition tools in TinyOS that is provided by NesC – Parameterized interfaces.
If you look a little bit carefully at rreport/ReportC.nc you will notice that we have replaced the User button component. ReportP.button is provided by UserC.Button. Let’s take a look in drivers/UserC.nc. It isn’t a module that does a bunch of bit twiddling, it is a configuration that builds the button interface out of a very general purpose driver that provides access to each and every pin on the MSP430. The UserInt button just happens to be the thing attached to Port 2, Pin 7.
configuration UserC {
provides interface
Button;
}
implementation {
components UserP;
Button =
UserP.Button;
components KernelC;
UserP.Boot ->
KernelC.Boot;
components
Msp430PortsC;
UserP.Pin -> Msp430PortsC.Port2[7]; /* UserInt */
UserP.PinInt ->
Msp430PortsC.Port2Interrupt[7];
}
The mapping from pins to buttons is provided by the helper module, UserP.nc. It is reproduced here.
module UserP {
provides interface
Button;
uses interface Boot;
uses interface
Msp430Port as Pin;
uses interface
Msp430PortInterrupt as PinInt;
}
implementation {
event void
Boot.booted() {
call
Pin.setDirection(0); /* Input */
call
PinInt.edge(1); /* Rising edge, button
release */
call
PinInt.enable(1); /* Enable interrupts
*/
}
/* Button processing task */
task void fire() {
signal Button.pressed(); /* Signal event to upper layers */
}
async event void
PinInt.fired() {
post fire();
}
}
This introduces two new interfaces, Msp430Port and Msp430PortInterrupt. The first can be used with any GIO pin, whereas only ports 1 and 2 provide interrupts. The setDirection command determines whether the pin is an input or output. We leave the interrupt sensitive to the rising edge. This corresponds to releasing the button. Finally, we enable interrupts.
Question: How would we modify this abstraction if we wanted it to signal both the press and release?
This simple example shows some of the power of component composition. We introduced a powerful driver infrastructure – the Msp430PortsC component - and then wired in an adapter module – UserP – in a configuration that preserved the old button interface.
The Msp430PortsC component shows an additional level of power. First, look at the declaration section of Msp430PortsC.nc. It is reproduced here.
configuration Msp430PortsC {
provides interface
Msp430Port as Port1[ uint8_t pin ];
provides interface
Msp430Port as Port2[ uint8_t pin ];
provides interface Msp430Port as Port3[ uint8_t pin ];
provides interface Msp430Port as Port5[ uint8_t pin ];
provides interface Msp430PortInterrupt as Port1Interrupt[ uint8_t pin ];
provides interface Msp430PortInterrupt as Port2Interrupt[ uint8_t pin ];
}
The parameter to the interface in square brackets indicates that this is an array of interfaces and that the interface index is associated with the variable pin of type 8 bit unsigned integer. The actual interfaces that are present in this array is determined at compile time by what gets wired to it.
For each port, the configuration passes the entire array of interfaces to the Msp430PortsP implementation module. This does all the low level bit twiddling and atomicity management. In addition, the interrupt signals are wired to the kernel. Looking back up at UserC, we see that user button is wired to the pin at index 7 of port 2. This wiring instantiates that interface.
Looking down into Msp430PortsP.nc, we see a generalization of what we did for LEDs and Buttons. In the declaration section it provides all the parameterized interfaces that were made visible in Msp430PortsC and it uses the parameterized HplSignal interfaces that are provided by the kernel.
module Msp430PortsP {
provides interface Msp430Port as Port1[ uint8_t pin ];
provides interface Msp430PortInterrupt as Port1Interrupt[ uint8_t pin ];
provides interface Msp430Port as Port2[ uint8_t pin ];
provides interface Msp430PortInterrupt as Port2Interrupt[ uint8_t pin ];
provides interface Msp430Port as Port3[ uint8_t pin ];
provides interface Msp430Port as Port4[ uint8_t pin ];
provides interface Msp430Port as Port5[ uint8_t pin ];
provides interface Msp430Port as Port6[ uint8_t pin ];
uses
interface HplSignal as HplSignalPort1;
uses interface
HplSignal as HplSignalPort2;
}
The implementation of the commands and events for each of the ports is parameterized by the pin. For example, the following get command is similar in function to what we saw previously, but the bit it selects out of the P1IN register is determined by the interface index.
implementation {
async command bool
Port1.get[ uint8_t pin ]() {
return (P1IN
>> pin) & 1;
}
…
}
The interrupt handler is especially interesting. It scans through all the bits in the interrupt flag register for the port (P1IFG) and signals an event to whatever handlers are wired to it. In effect, we have added a layer of interrupt dispatch, so higher level components can take the view that pins have individual interrupts. Note that this signal is still in interrupt context, so those higher level handlers must do their work quickly, post a task and return.
The default handler for the fired event catches any interrupts for pins that do not have a higher level component wired to them.
async event void HplSignalPort1.fired()
{
int pin;
for ( pin = 0; pin<8; pin++ ) {
if ( P1IFG & (1 << pin) ) {
signal
Port1Interrupt.fired[ pin ]();
P1IFG
&= ~(1 << pin);
}
}
}
default async event void Port1Interrupt.fired[
uint8_t pin ]() {}
You will certainly want to switch your application over to this more capable infrastructure. You may even want to skip the UserC interface and go directly to Msp430Port and Msp430PortInterrupt.