📄 storage - tinyos documentation wiki.htm
字号:
memory. A volume table might look like: </P><PRE><volume_table>
<volume name="CONFIGLOG" size="65536"/>
<volume name="PACKETLOG" size="65536"/>
<volume name="SENSORLOG" size="131072"/>
<volume name="CAMERALOG" size="524288"/>
</volume_table>
</PRE>
<P>The volume table for a particular application must be placed in the
application's directory (where one types 'make') and must be named
<CODE>volumes-CHIPNAME.xml</CODE> where CHIPNAME is replaced with the
platform-specific flash chip's name. For example, the Telos mote uses the ST
Microelectronics M25P family of flash memories. The drivers for these chips can
be found in the <CODE>tos/chips/stm25p</CODE> directory. Therefore, a
Telos-based application that uses the storage abstractions needs a file named
<CODE>volumes-stm25p.xml</CODE>. </P>
<P>Note that the size parameter is a multiple of the erase unit for a particular
flash chip. See Section 4.1 in TEP 103 <SUP class=reference id=_ref-fn1_0><A
title="" href="http://docs.tinyos.net/index.php/Storage#_note-fn1">[1]</A></SUP>
for more details. </P><A name=Storing_Configuration_Data></A>
<H1><SPAN class=mw-headline>Storing Configuration Data</SPAN></H1>
<P>This lesson shows how configuration data can be written to and read from
non-volatile storage. Configuration data typically exhibit some subset of the
following properties. They are <B>limited in size</B>, ranging from a fews tens
to a couple hundred bytes. Their values may be <B>non-uniform</B> across nodes.
Sometimes, their values are <B>unknown</B> prior to deployment in the field and
sometimes their values are <B>hardware-specific</B>, rather than being tied to
the software running on a node. </P>
<P>Because configuration data can be non-uniform across nodes or unknown <I>a
priori</I>, their values may be difficult to specify at compile-time and since
the data are sometimes hardware-specific, their values must survive
reprogramming, suggesting that encoding these values in the program image is not
the simplest approach. Storing configuration data in volatile memory is also
problematic since this data would not survive a reset or power cycle. </P>
<P>In summary, configuration data must persist through node resets, power
cycles, or reprogramming, and then be restored afterward. The ability to persist
and restore configuration data in this manner is useful in many scenarios. </P>
<UL>
<LI><B>Calibration.</B> Calibration coefficients for sensors might be
factory-configured and persisted, so they are not lost when power is removed
for shipping or the node is reprogrammed post-calibration. For example, a
hypothetical temperature sensor might have an offset and gain that must be
calibrated, because these parameters are hardware-specific, and stored because
they are needed to convert the output voltage into the more useful units of
degrees Celcius. The calibration data for such a sensor might look like: <PRE>typedef struct calibration_config_t {
int16_t temp_offset;
int16_t temp_gain;
} calibration_config_t;
</PRE>
<LI><B>Identification.</B> Device identification information, like
IEEE-compliant MAC addresses or the TinyOS TOS_NODE_ID parameters are
non-uniform across nodes although they are not hardware-specific, once they
are assigned to a node, these values should be <I>sticky</I> in that they are
persisted across reset, power cycle, and reprogramming operations (and not
lost or reassigned to another node). <PRE>typedef struct radio_config_t {
ieee_mac_addr_t mac;
uint16_t tos_node_id;
} radio_config_t;
</PRE>
<LI><B>Location.</B> Node location data may be unknown at compile-time and
only become available during deployment. An application might, for example,
store node coordinates as follows and update these values in the field: <PRE>typedef struct coord_config_t {
uint16_t x;
uint16_t y;
uint16_t z;
} coord_config_t;
</PRE>
<LI><B>Sensing.</B> Sensing and signal processing parameters like sample
period, filter coefficients, and detection thresholds might be adjusted in the
field. The configuration data for such an application might look like: <PRE>typedef struct sense_config_t {
uint16_t temp_sample_period_milli;
uint16_t temp_ema_alpha_numerator;
uint16_t temp_ema_alpha_denominator;
uint16_t temp_high_threshold;
uint16_t temp_low_threshold;
} sense_config_t;
</PRE></LI></UL>
<P>Now that we have discussed <I>why</I> one might use this type of storage,
let's see <I>how</I> to use it. We will implement a simple demo application that
illustrates how to use the <CODE>Mount</CODE> and <CODE>ConfigStorage</CODE>
abstractions. A timer period will be read from flash, divided by two, and
written back to flash. An LED is toggled each time the timer fires. But, before
diving into code, let's discuss some high-level design considerations. </P>
<P>See <A class="external text"
title=http://www.tinyos.net/tinyos-2.x/apps/tutorials/BlinkConfig/
href="http://www.tinyos.net/tinyos-2.x/apps/tutorials/BlinkConfig/"
rel=nofollow><CODE>tinyos-2.x/apps/tutorials/BlinkConfig/</CODE></A> for the
accompanying code. </P>
<P>Prior to its first usage, a volume does not contain any valid data. So, our
code should detect the first usage of a volume and take any appropriate actions
(e.g. preload it with default values). Similarly, when the data layout of the
volume changes (for example, if the application requires new or different
configuration variables), then application code should detect this and take
appropriate actions (e.g. migrate the old data to the new layout or erase the
volume and reload the defaults). These requirements suggest that we should have
a way of keeping track of the volume version. We will use a version number for
this purpose (and will need to maintain a discipline of updating the version
number when the data layout changes incompatibly). Our configuration struct
might have the following fields for the version number and blink period: </P><PRE>typedef struct config_t {
uint16_t version;
uint16_t period;
} config_t;
</PRE>
<OL>
<LI>Create a <CODE>volumes-CHIPNAME.xml</CODE> file, enter the volume table in
this file, and place the file in the application directory. Note that
<CODE>CHIPNAME</CODE> is the flash chip used on your target plaform. For
example, <CODE>CHIPNAME</CODE> will be <CODE>stm25p</CODE> for the Telos
platform and <CODE>at45db</CODE> for the MicaZ platform. Our file will have
the following contents: <PRE><volume_table>
<volume name="LOGTEST" size="262144"/>
<volume name="CONFIGTEST" size="131072"/>
</volume_table>
This volume information is used by the toolchain to create an include file. The auto-generated file, however, has to be included manually.
Place the following line in the configuration file which declares the ConfigStorageC component (e.g. <CODE>BlinkConfigAppC.nc</CODE>):
#include "StorageVolumes.h"
</PRE>
<LI>BlinkConfigC, the application code for this simple demo, <I>uses</I> the
<CODE>Mount</CODE> and <CODE>ConfigStorage</CODE> interfaces (note that we
rename <CODE>ConfigStorage</CODE> to <CODE>Config</CODE>). <PRE>module BlinkConfigC {
uses {
...
interface ConfigStorage as Config;
interface Mount;
...
}
}
</PRE>
<LI>Each interface must be wired to an <I>implementation</I> that will provide
it: <PRE>configuration BlinkConfigAppC {
}
implementation {
components BlinkConfigC as App;
components new ConfigStorageC(VOLUME_CONFIGTEST);
...
App.Config -> ConfigStorageC.ConfigStorage;
App.Mount -> ConfigStorageC.Mount;
...
}
</PRE>
<LI>Before the flash chip can be used, it must be mounted using the two-phase
mount/mountDone command. Here we show how this might be chained into the boot
sequence: <PRE> event void Boot.booted() {
conf.period = DEFAULT_PERIOD;
if (call Mount.mount() != SUCCESS) {
// Handle failure
}
}
</PRE>
<LI>If the Mount.mount succeeds, then the <CODE>Mount.mountDone</CODE> event
will be signaled. The following code shows how to check if the volume is
valid, and if it is, how to initiate a read from the volume using the
<CODE>ConfigStore.read</CODE> command. If the volume is invalid, calling
<CODE>Config.commit</CODE> will make it valid (this call is also used to flush
buffered data to flash much like the UNIX fsync system call is supposed to
flush buffered writes to disk): <PRE> event void Mount.mountDone(error_t error) {
if (error == SUCCESS) {
if (call Config.valid() == TRUE) {
if (call Config.read(CONFIG_ADDR, &conf, sizeof(conf)) != SUCCESS) {
// Handle failure
}
}
else {
// Invalid volume. Commit to make valid.
call Leds.led1On();
if (call Config.commit() == SUCCESS) {
call Leds.led0On();
}
else {
// Handle failure
}
}
}
else{
// Handle failure
}
}
</PRE>
<LI>If the read is successful, then a <CODE>Config.readDone</CODE> event will
occur. In this case, we first check for a successful read, and if successful,
we then check the version number. If the version number matches what we
expected, we copy of the configuration data to a local variable, and adjust
its values. If there is a version mismatch, we set the value of the
configuration information to a default value. Finally, we call the the
<CODE>Config.write</CODE> function: <PRE> event void Config.readDone(storage_addr_t addr, void* buf,
storage_len_t len, error_t err) __attribute__((noinline)) {
if (err == SUCCESS) {
memcpy(&conf, buf, len);
if (conf.version == CONFIG_VERSION) {
conf.period = conf.period/2;
conf.period = conf.period > MAX_PERIOD ? MAX_PERIOD : conf.period;
conf.period = conf.period < MIN_PERIOD ? MAX_PERIOD : conf.period;
}
else {
// Version mismatch. Restore default.
call Leds.led1On();
conf.version = CONFIG_VERSION;
conf.period = DEFAULT_PERIOD;
}
call Leds.led0On();
call Config.write(CONFIG_ADDR, &conf, sizeof(conf));
}
else {
// Handle failure.
}
}
</PRE>
<LI>Data is not necessarily "written" to flash when
<CODE>ConfigStore.write</CODE> is called and <CODE>Config.writeDone</CODE> is
signaled. To ensure data is persisted to flash, a
<CODE>ConfigStore.commit</CODE> call is required: <PRE> event void Config.writeDone(storage_addr_t addr, void *buf,
storage_len_t len, error_t err) {
// Verify addr and len
if (err == SUCCESS) {
if (call Config.commit() != SUCCESS) {
// Handle failure
}
}
else {
// Handle failure
}
}
</PRE>
<LI>Finally, when the <CODE>Config.commitDone</CODE> event is signaled, data
has been durably written to flash and will survive a node power cycle: <PRE> event void Config.commitDone(error_t err) {
call Leds.led0Off();
call Timer0.startPeriodic(conf.period);
if (err == SUCCESS) {
// Handle failure
}
}
</PRE></LI></OL><A name=Logging_Data></A>
<H1><SPAN class=mw-headline>Logging Data</SPAN></H1>
<P>Reliable (atomic) logging of events and small data items is a common
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -