--- /dev/null
+<html>
+ <head>
+ <title>TinyOS Tutorial Lesson 16: Writing Low Power Sensing Applications</title>
+ <link href="../../stylesheets/tutorial.css" rel="stylesheet" Type="text/css">
+ </head>
+ <body>
+
+ <div class="title">Lesson 16: Writing Low Power Sensing Applications</div>
+ <div class="subtitle">Last updated August 31st, 2007</div>
+
+ <p>
+ This lesson demonstrates how to write low power sensing applications in TinyOS. At
+ any given moment, the power consumption of a wireless sensor node is a function of its
+ microcontroller power state, whether the radio, flash, and sensor peripherals are on,
+ and what operations active peripherals are performing. This tutorial shows you
+ how to best utilize the features provided by TinyOS to keep the power consumption
+ of applications that use these devices to a minumum.
+ </p>
+
+ <h1>Overview</h1>
+ <p>
+ Energy management is a critical concern in wireless sensor networks. Without it,
+ the lifetime of a wireless sensor node is limited to just a few short weeks or even
+ days, depending on the type of application it is running and the size of batteries
+ it is using. With proper energy management, the same node, running the
+ same application, can be made to last for months or years on the same set of batteries.
+ </p>
+ <p>
+ Ideally, one would want all energy management to be handled transparently by the
+ operating system, relieving application developers from having to deal with this
+ burden themselves. While attempts have been made in the past to provide such
+ capabilities, most operating systems today still just provide the necessary primitives for
+ performing energy management -- the logic of when and how to do so is left completely up
+ to the application.
+ </p>
+ <p>
+ TinyOS provides a novel method of performing energy management directly
+ within the OS. The method it uses is as efficient as it is elegant. Developers
+ no longer have to struggle with code that explicitly manages the power state for any of the
+ peripheral devices that it uses. To get a better idea of the complexity involved in
+ doing something as simple as periodically taking a set of sensor readings
+ and logging them to flash in the most energy efficient manner possible, take a look
+ at the pseudo code found below.
+ </p>
+ <pre>
+Every Sample Period:
+ Turn on SPI bus
+ Turn on flash chip
+ Turn on voltage reference
+ Turn on I2C bus
+ Log prior readings
+ Start humidity sample
+ Wait 5ms for log
+ Turn off flash chip
+ Turn off SPI bus
+ Wait 12ms for vref
+ Turn on ADC
+ Start total solar sample
+ Wait 2ms for total solar
+ Start photo active sample
+ Wait 2ms for photo active
+ Turn off ADC
+ Turn off vref
+ Wait 34ms for humidity
+ Start temperature sample
+ Wait 220ms for temperature
+ Turn off I2C bus
+ </pre>
+ <p>
+ With the methods provided by TinyOS, hand-tuned application code that looks like that
+ can be transformed into this:
+ </p>
+ <pre>
+Every Sample Period:
+ Log prior readings
+ Sample photo active
+ Sample total solar
+ Sample temperature
+ Sample humidity
+ </pre>
+ <p>
+ The pseudo code shown above is for concurrently sampling all of the
+ the onboard sensors found on the latest revision of the tmote sky sensor nodes.
+ Experiments have shown that using the methods provided by TinyOS, this application
+ comes within 1.6% of the energy efficiency of the hand-tuned implementation --
+ even at sampling rates as fast as 1 sample per second. For more information
+ on these experiments and the method TinyOS uses to actually manage energy
+ so efficiently, please refer to the paper found
+ <a href="http://sing.stanford.edu/klueska/Black_Site/Publications_files/klues07icem.pdf">
+ here.</a>
+ </p>
+ <!---
+ <center>
+ <img src="img/tmote_current.png" width=66% ></img>
+ </center>
+ -->
+ <p>
+ The rest of this tutorial is dedicated to teaching you how to write applications
+ that allow TinyOS to manage energy for you in the most efficient manner possible.
+ As a rule, TinyOS can manage energy more efficiently when I/O requests are submitted
+ by an application in parallel. Submitting them serially may result in energy
+ wasted by unnecessarily turning devices on and off more frequently than required.
+ </p>
+ <h1>
+ Peripheral Energy Management
+ </h1>
+ <p>
+ Compare the following two code snippets:
+ </p>
+<table>
+ <tr><td><b>Parallel</b></td><td width=10></td><td><b>Serial</b></td></tr>
+ <tr><td valign=top>
+<pre>
+event void Boot.booted() {
+ call Timer.startPeriodic(SAMPLE_PERIOD);
+}
+
+event void Timer.fired() {
+ call LogWrite.append(current_entry, sizeof(log_entry_t));
+
+ current_entry_num = !current_entry_num;
+ current_entry = &(entry[current_entry_num]);
+ current_entry->entry_num = ++log_entry_num;
+
+ call Humidity.read();
+ call Temperature.read();
+ call Photo.read();
+ call Radiation.read();
+}
+
+event void Humidity.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ current_entry->hum = val;
+ }
+ else current_entry->hum = 0xFFFF;
+}
+
+event void Temperature.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ current_entry->temp = val;
+ }
+ else current_entry->temp = 0xFFFF;
+}
+
+event void Photo.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ current_entry->photo = val;
+ }
+ else current_entry->photo = 0xFFFF;
+ }
+
+event void Radiation.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ current_entry->rad = val;
+ }
+ else current_entry->rad = 0xFFFF;
+}
+
+event void LogWrite.appendDone(void* buf,
+ storage_len_t len,
+ bool recordsLost,
+ error_t error) {
+ if (error == SUCCESS)
+ call Leds.led2Toggle();
+}
+</pre>
+</td>
+<td></td>
+<td valign=top>
+<pre>
+event void Boot.booted() {
+ call Timer.startPeriodic(SAMPLE_PERIOD);
+}
+
+event void Timer.fired() {
+ call Humidity.read();
+}
+
+event void Humidity.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ entry->rad = val;
+ }
+ else current_entry->rad = 0xFFFF;
+ call Temperature.read();
+}
+
+event void Temperature.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ entry->rad = val;
+ }
+ else current_entry->rad = 0xFFFF;
+ call Photo.read();
+}
+
+event void Photo.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ entry->rad = val;
+ }
+ else current_entry->rad = 0xFFFF;
+ call Radiation.read();
+}
+
+event void Radiation.readDone(error_t result, uint16_t val) {
+ if(result == SUCCESS) {
+ entry->rad = val;
+ }
+ else current_entry->rad = 0xFFFF;
+ call LogWrite.append(entry, sizeof(log_entry_t));
+}
+
+event void LogWrite.appendDone(void* buf,
+ storage_len_t len,
+ bool recordsLost,
+ error_t error) {
+ if (error == SUCCESS)
+ call Leds.led2Toggle();
+}
+
+
+
+
+
+ </pre>
+ </td>
+ </tr>
+ </table>
+ <p>
+ In the parallel case, logging to flash and requesting samples from each sensor is
+ all done within the body of the <code>Timer.fired()</code> event. In the serial case,
+ a chain of events is triggered by first calling <code>Humidity.read()</code> in
+ <code>Timer.fired()</code>, sampling each subsequent sensor in the body of the previous
+ <code>readDone()</code> event, and ending with all sensor readings
+ being logged to flash.
+ </p>
+ <p>
+ By logging to flash and sampling all sensors within the body of a single event, the OS
+ has the opportunity to schedule each operation as it sees fit. Performing each operation
+ after the completion of the previous one gives the OS no such opportunity. The only
+ downside of the parallel approach is that the application must manually manage a double
+ buffer so that the values written to flash are not overwritten before they are logged. To save
+ the most energy, however, the parallel version should always be used.
+ Keep in mind that in both cases, the developer must also make sure that the sampling
+ interval is longer than the time it takes to gather all sensor readings. If it is not,
+ data corruption will inevitably occur.
+ </p>
+ <h1>
+ Radio Power Management
+ </h1>
+ <p>
+ By default, TinyOS provides low power radio operation through a technique known as
+ <i>Low-Power Listening</i>. In low-power listening, a node turns on
+ its radio just long enough to detect a carrier on the channel.
+ If it detects a carrier, then it keeps the radio on long enough to detect a packet.
+ Because the LPL check period is much longer than a packet, a transmitter must send
+ its first packet enough times for a receiver to have a chance to hear it. The
+ transmitter stops sending once it receives a link-layer acknowledgment or a timeout
+ of twice the check period. When a node receives a packet, it stays awake long
+ enough to receive a second packet. Therefore, a packet burst amortizes the wakeup
+ cost of the first packet over the follow-up packets. It is therefore more energy
+ efficient to send packets in bursts when using low-power listening than sending individual
+ packets at some fixed constant rate. Keep this in mind when developing applications
+ that require low power operation.
+ </p>
+ <p>
+ Controlling the operation of low-power listening in TinyOS is provided through the
+ use of the <code>LowPowerListening</code> interface. Currently, this interface
+ is only supported for the cc1000 and cc2420 radios. In the future we hope to
+ support other radio platforms as well.
+ </p>
+ <pre>
+interface LowPowerListening {
+ /**
+ * Set this this node's radio sleep interval, in milliseconds.
+ * Once every interval, the node will sleep and perform an Rx check
+ * on the radio. Setting the sleep interval to 0 will keep the radio
+ * always on.
+ *
+ * This is the equivalent of setting the local duty cycle rate.
+ *
+ * @param sleepIntervalMs the length of this node's Rx check interval, in [ms]
+ */
+ command void setLocalSleepInterval(uint16_t sleepIntervalMs);
+
+ /**
+ * @return the local node's sleep interval, in [ms]
+ */
+ command uint16_t getLocalSleepInterval();
+
+ /**
+ * Set this node's radio duty cycle rate, in units of [percentage*100].
+ * For example, to get a 0.05% duty cycle,
+ * call LowPowerListening.setDutyCycle(5);
+ *
+ * For a 100% duty cycle (always on),
+ * call LowPowerListening.setDutyCycle(10000);
+ *
+ * This is the equivalent of setting the local sleep interval explicitly.
+ *
+ * @param dutyCycle The duty cycle percentage, in units of [percentage*100]
+ */
+ command void setLocalDutyCycle(uint16_t dutyCycle);
+
+ /**
+ * @return this node's radio duty cycle rate, in units of [percentage*100]
+ */
+ command uint16_t getLocalDutyCycle();
+
+ /**
+ * Configure this outgoing message so it can be transmitted to a neighbor mote
+ * with the specified Rx sleep interval.
+ * @param msg Pointer to the message that will be sent
+ * @param sleepInterval The receiving node's sleep interval, in [ms]
+ */
+ command void setRxSleepInterval(message_t *msg, uint16_t sleepIntervalMs);
+
+ /**
+ * @return the destination node's sleep interval configured in this message
+ */
+ command uint16_t getRxSleepInterval(message_t *msg);
+
+ /**
+ * Configure this outgoing message so it can be transmitted to a neighbor mote
+ * with the specified Rx duty cycle rate.
+ * Duty cycle is in units of [percentage*100], i.e. 0.25% duty cycle = 25.
+ *
+ * @param msg Pointer to the message that will be sent
+ * @param dutyCycle The duty cycle of the receiving mote, in units of
+ * [percentage*100]
+ */
+ command void setRxDutyCycle(message_t *msg, uint16_t dutyCycle);
+
+ /**
+ * @return the destination node's duty cycle configured in this message
+ * in units of [percentage*100]
+ */
+ command uint16_t getRxDutyCycle(message_t *msg);
+
+ /**
+ * Convert a duty cycle, in units of [percentage*100], to
+ * the sleep interval of the mote in milliseconds
+ * @param dutyCycle The duty cycle in units of [percentage*100]
+ * @return The equivalent sleep interval, in units of [ms]
+ */
+ command uint16_t dutyCycleToSleepInterval(uint16_t dutyCycle);
+
+ /**
+ * Convert a sleep interval, in units of [ms], to a duty cycle
+ * in units of [percentage*100]
+ * @param sleepInterval The sleep interval in units of [ms]
+ * @return The duty cycle in units of [percentage*100]
+ */
+ command uint16_t sleepIntervalToDutyCycle(uint16_t sleepInterval);
+
+}</pre>
+<p>
+This interface is located in <code>tos/interfaces</code> in the standard TinyOS tree. Take a
+look at the comments for each command to familiarize yourself with how this interface can be used.
+</p>
+<p>
+Using this interface typically involves first setting a nodes local duty cycle within the
+<code>Boot.booted()</code> event of the top level application. For each packet the application
+wishes to send, the duty cycle of its destination is then specified as metadata so that the
+correct number of preambles can be prepended to it. The code snippet found below demonstrates
+this usage pattern:
+</p>
+<pre>
+event void Boot.booted() {
+ call LPL.setLocalSleepInterval(LPL_INTERVAL);
+ call AMControl.start();
+}
+
+event void AMControl.startDone(error_t e) {
+ if(e != SUCCESS)
+ call AMControl.start();
+}
+
+...
+
+void sendMsg() {
+ call LPL.setRxSleepInterval(&msg, LPL_INTERVAL);
+ if(call Send.send(dest_addr, &msg, sizeof(my_msg_t)) != SUCCESS)
+ post retrySendTask();
+}</pre>
+
+The <code>AMControl</code> interface is provided by <code>ActiveMessageC</code>, and is
+used, among other things, to enable the operation of low-power listening for the radio.
+Once <code>AMControl.start()</code> has completed successfully, the radio begins to duty cycle
+itself as specified by the parameter to the <code>setLocalSleepInterval()</code> command. Calling
+<code>setRxSleepInterval()</code> with a specific sleep interval then allows the correct number of preambles to be sent
+for the message specified in its parameter list.
+
+<h1>
+Microcontroller Power Management
+</h1>
+<p>
+Microcontrollers often have several power states, with varying power draws,
+wakeup latencies, and peripheral support. The microcontroller should always be
+in the lowest possible power state that can satisfy application requirements.
+Determining this state accurately requires knowing a great deal about the power
+state of many subsystems and their peripherals. Additionally, state transitions
+are common. Every time a microcontroller handles an interrupt, it moves from a low
+power state to an active state, and whenever the TinyOS scheduler finds the task
+queue empty it returns the microcontroller to a low power state. TinyOS uses
+three mechanisms to decide what low power state it puts a microcontroller into:
+status and control registers, a dirty bit, and a power state override.
+Please refer to <a href=../tep112.html>TEP 112</a> for more information.
+</p>
+</p>
+As a developer, you will not have to worry about MCU power managment at all in
+most situations. TinyOS handles everything for you automatically. At times,
+however, you may want to use the provided power state override functionality.
+Take a look at <code>tos/chips/atm128/timer/HplAtm128Timer0AsyncP.nc</code> if
+you are interested in seeing an example of where this override functionality is used.
+</p>
+
+<h1>
+Low Power Sensing Application
+</h1>
+A fully functional low-power sensing application that combines each of the techniques
+found in this tutorial can be found in <code>apps/tutorials/LowPowerSensing</code>. This
+application has been tested on telosb and mica2 platforms, but should be usable
+on others without modification. Take a look at the README file found in the top level directory
+for more information.
+
+<a name=#related_docs>
+<h1>Related Documentation</h1>
+</a>
+<ul>
+<li> <a href="../tep103.html">TEP 103: Permanent Data Storage (Flash)</a>
+<li> <a href="../tep105.html">TEP 105: Low Power Listening</a>
+<li> <a href="../tep108.html">TEP 108: Resource Arbitration</a>
+<li> <a href="../tep109.html">TEP 109: Sensors and Sensor Boards</a>
+<li> <a href="../tep112.html">TEP 112: Microcontroller Power Management</a>
+<li> <a href="../tep114.html">TEP 114: SIDs: Source and Sink Independent Drivers</a>
+<li> <a href="../tep115.html">TEP 115: Power Management of Non-Virtualised Devices</a>
+<li> <a href="http://sing.stanford.edu/klueska/Black_Site/Publications_files/klues07icem.pdf">
+Integrating Concurrency Control and Energy Management in Device Drivers</a>
+</ul>
+
+<!-- Begin footer -->
+<br>
+<hr>
+<center>
+<p>< <b><a href="lesson15.html">Previous Lesson</a></b> | <b><a
+ href="index.html">Top</a></b> |
+</center>
+
+</body>
+</html>