Chapter 5: Developing for the .NET Micro Framework


Retaining Data

To be truly useful, a device must be able to retain data. On a large computer with an operating system, you can save information by writing it to a file. Files are stored in persistent media (for example, a magnetic disk) and can be reloaded later. The operating system provides support for a filing system, which uses named files to hold information that is used by programs. When you want to save something, you open a file and write to it. To retrieve the information, you can open and read the same file. The .NET Micro Framework gets by without an operating system, but we still need a way to retain data on the device.

Data Storage Hardware

Computer systems frequently use magnetic disks as storage devices. However, it is unlikely that a .NET Micro Framework device will be fitted with a disk drive; instead it will use some form of memory device that retains its contents even when powered off. Such "nonvolatile" memory storage can take the form of battery backed-up conventional memory (as used in many desktop computers to hold their system settings) or electrically erasable programmable read-only memory (EEPROM), often referred to as "flash" memory and used in digital cameras and other portable media devices. We are going to use flash memory to store data; it is fitted to a large number of .NET Micro Framework devices.

Flash memory is cheap to make, and very large capacity devices are available. Data written to a flash device is held until erased. However, unlike Random Access Memory, where individual memory locations can be overwritten with new values, once you have written a value into a particular location in flash memory, you cannot rewrite that location. Instead, you must clear an entire region, that is, although you can write individual values into flash, you must clear a whole area of the memory at once. There is also a limit to the number of times that you can write to and erase a flash device before it ceases to work. Thus, you cannot use it as normal program memory, and you need to be careful how much reading and writing you perform. The .NET Micro Framework provides a storage mechanism based on flash memory, which uses cycling techniques that spread writes and erases over the address space of the device to minimize the wearing effect.

All we have to do is use the methods provided by the .NET Micro Framework to retain the data, and it will store and manage the data for us. The .NET Micro Framework carefully manages the nonvolatile storage and takes steps to ensure that important data, for example, system settings, is not overwritten if the space available fills up. As we will see in the section titled "Setting Extended Weak Reference Properties" later in this chapter, we can flag data we wish to store with an importance level that is used to determine whether it will discard data to make room for other data.

Storing Data

We can store a range of data on our device, from operator settings to information gathered during use, perhaps for data logging. We will start by storing some logging information about flashlight use. In Chapter 4, "Building a Device," we created a class that holds information about the use of our flashlight. Each time a user turns a flashlight off, the flashlight's program creates a use-log entry and adds a reference to it to the ArrayList that holds all the use records.

 static void setLampState(bool state) {    Debug.Print("lamp state change");    System.Threading.Monitor.Enter(switchInterrupt);    lampOutput.Write(state);    System.Threading.Monitor.Exit(switchInterrupt);    if (state)    {       // turning the lamp on - record the on time       switchedOn = DateTime.Now;    }    else       {       // turning the lamp off - add a use record to the arraylist       FlashlightUse record = new FlashlightUse(switchedOn, DateTime.Now);       flashlightLog.AddToLog(record);    } } 

The program executes the preceding code whenever the flashlight lamp changes state. If the new state of the lamp is off, the code creates a FlashlightUse instance and adds it to the log. If the new state of the light is on, the code records the date and time, making it available for use when creating the log record later.

We will use the extended weak reference support provided by the .NET Micro Framework to store the log information. When the program starts, it reads the data from the persistent storage and can store it again later. We can use this to track the battery life and usage of the flashlight. Even if the flashlight loses all power, the readings that have been taken will be retained. In Chapter 4, we already saw how to export this stored data from the flashlight through the COM port. You can use these techniques to implement any form of data logging on a .NET Micro Framework device.

When you use a filing system, you create a connection to a storage device and then use this connection to perform the required action. In C# programming terms, such a connection is called a stream. You can use a stream instance for reading, writing, or both. Storing data in the .NET Micro Framework is slightly different. It makes use of extended weak references to manage the act of storing the data.

Weak References

To understand what an extended weak reference is, you first have to understand how weak references work in C#. Consider the following code:

 FlashlightUse record = new FlashlightUse(switchedOn, DateTime.Now); 

This creates an instance of the FlashLightUse class and then makes the reference record refer to it. You can visualize this as a tag called record that is tied to an object in memory. However, think what the following line of code does:

 record = null; 

The null keyword means record points nowhere. In other words, the line of code causes the program to untie the tag from any instance and explicitly set it to not point at anything. The result of the assignment to null is that the object that had the tag record tied to it now has nothing tied to it, that is, it is inaccessible to our program. It is like a balloon on the end of a piece of ribbon. If you untie the ribbon, the balloon will float away. In the case of our program, the assignment of null to the record reference means that we have an object in memory that the code cannot use. If these statements were executed in a C++ program, in which the programmer must manage the allocation and freeing of memory, this would result in the memory being unusable. Fortunately, the C# language provides a means by which such memory can be recovered.

Like the .NET Framework, the .NET Micro Framework has a garbage collector (GC) process that goes through memory looking for inaccessible objects, removing them and recovering the memory they occupy. This is an important task; without it, your programs would grind to a halt if they created and released large numbers of objects, filling memory with inaccessible items.

The GC in the .NET Micro Framework works in a manner specifically designed for small processor configurations. When the GC runs, it looks for objects that have no references to them, and it deletes these objects.

If an object has a reference to it, the program may still use the object, and therefore the GC cannot delete it. These are strong references because they prevent the removal of the object to which they refer.

However, sometimes you want to have objects that are halfway between inaccessible and not. These are items that you would like to have around, but you don't really mind if the GC removes them from memory. You can visualize these as balloons held by very weak threads. Your program can access such items by following the weak thread, but if needed, the GC can break the thread and reclaim the memory that the object occupies.

Uses for Weak References

One situation in which weak references are useful is when your program is caching items downloaded from a network connection. You'd like to use the local memory in the device to make it unnecessary to fetch an item if you need it again, but you know that if you set up strong references to all the fetched items, they will sit in memory until you explicitly remove these references. It might be a lot of work to decide which objects you need, and you'd rather not have to do that.

What you want to do is be able to say to the garbage collector, "If you need to remove the item on the end of this reference, you can, but otherwise I'd like you to keep it around." If memory becomes tight, you will lose your cached items and have to go and fetch them again. This will cause your program to run more slowly because it has to reload any items removed by the GC, but the program will not fail as long as you test for validity before you use your weak reference.

Another situation in which a weak reference is useful is when you want to prevent a reference in a data structure from preventing the collection of an instance by the GC. Sometimes object A refers to object B, and object B refers to A. If the reference between B and A is a strong one, the GC cannot remove A, because it still has a strong reference connected to it from B. If the reference from B to A is weak, the GC can delete A.

Creating a Weak Reference

Weak references are not specific to the .NET Micro Framework. The .NET Framework implements them in a special class.

 FlashlightUse record = new FlashlightUse(switchedOn, DateTime.Now); WeakReference weakRecord = new WeakReference(record); 

This code creates an instance of a FlashlightUse class and then creates a WeakReference that refers to this instance. Note that code passes the target of the weak reference into its constructor. We can now use the weak reference to obtain the object, as required:

 if (weakRecord.IsAlive) {    FlashlightUse temp = (FlashlightUse) weakRecord.Target;    Debug.Print("Date On:"+ temp.switchedOn.ToString()); } 

The instance contains a property named Target, which is a reference to the item that the weak reference is pointing at. This code fetches the target from the weak reference and prints out the date on which the flashlight was switched on. Note that we have to cast this reference to get hold of the item that we originally set the reference to refer to. The weak reference instance also provides a property named IsAlive, which is set to true if the reference still refers to the target.

Note 

There may be issues if the garbage collection process runs between the test of the IsAlive property and the actual use of the reference. This means that when you use the target of a weak reference, you should have some exception handling in place to ensure that your program will not fail if the reference is null.

Weak Delegates

As an aside, the .NET Micro Framework also provides weak delegates. These are directly analogous to weak references, in that if a method in an object has a weak delegate attached to it, the GC can destroy the object. Weak delegates are a way that you can have objects in memory that receive events, but the GC can remove those objects from memory, if required.

Retaining Data with Extended Weak References

Although we have now learned something potentially useful about object and memory management, we don't seem to have discovered how to store information using the .NET Micro Framework.

Extended weak references are, as the name implies, an extension of the weak reference mechanism. They provide an additional level of behavior for a weak reference, which causes an object to be stored when the weak reference target is set to refer to an item. If we want to store something, we create an extended weak reference and then make the reference refer to the thing we want to store.

 useLogExtendedWeakReference.Target = flashlightLog; 

This code sets the Target property of the extended weak reference useLogExtended-WeakReference to the flashlightLog instance. This triggers the save.

Writing to the flash memory may take a considerable amount of time, and there is no method of discovering when the save is complete. For this reason, if your program will end after the save action, it should pause first.

 System.Threading.Thread.Sleep(1000); 

This line will pause the executing thread for a second, which for reasonably sized items of data will be sufficient. You can modulate the size of the sleep, depending on the number of bytes to be persisted and the speed of the memory device in the device you are using.

As an example, the FreeScale i.MXS .NET Development kit uses an 8-MB Intel StrataFlash 28F640 flash memory device. This is specified as having an effective write speed of 6.8 micro seconds per byte. So it would take around .68 of a second to write 100K of data to the device. You are advised to allow time for the software to manage the cycling action to reduce memory wear by doubling this, giving a pause of around 1.4 seconds in this situation.

Serialization

The data in a class is stored on a flash memory device in serialized form. Serialization is a process by which an instance of a class is converted into a block of data that can be stored or transmitted. When an object is serialized, all its nonstatic members are stored. Any references the object contains to other objects are followed, and those are serialized, too. To serialize a class, the code must tag it with an attribute to indicate that it may be saved.

 [Serializable] private sealed class FlashlightUseLog {    public DateTime CreateTime = DateTime.Now;    public ArrayList UseLog = new ArrayList();    public void dumpLog()    {       foreach (FlashlightUse logItem in UseLog)       {          if (logItem != null) {Debug.Print(logItem.ToString());}       }    }    public void AddToLog(FlashlightUse use)    {       UseLog.Add(use);    } } 

The previous code marks the FlashlightUseLog class as serializable. Note that because the FlashlightUseLog class holds references to FlashlightUse instances, the Flashlight-Use class must also be marked serializable.

Note 

If the serialization action fails, the process will not result in an exception being thrown; instead, the item being persisted will not be saved. You must therefore be very careful to ensure that you flag all the data items you need to save as such.

Restoring Persisted Data

There is a complementary mechanism for restoring data from storage when the program restarts. This is, however, slightly more complicated than saving data, in that the very first time a program creates an extended weak reference, the program must handle it specially, as shown later in this section. We also need to configure the options for the storage behavior.

Creating an Extended Weak Reference

We are familiar with creating stream objects to connect programs to files. What we are now going to do is create an extended weak reference to connect our program with the serialized data from the class that we previously stored.

 // reference to our data private static ExtendedWeakReference useLogExtendedWeakReference; 

The preceding line declares an ExtendedWeakReference, which is a static member of the enclosing class.

Making useLogExtendedWeakReference a static member of the class means that the GC will never collect the extended weak reference that it refers to. Our program can now create an instance of an extended weak reference.

 useLogExtendedWeakReference = ExtendedWeakReference.RecoverOrCreate(         typeof(UseLogMarkerType),                               // marker class         0,                                                      // id number         ExtendedWeakReference.c_SurvivePowerdown);              // flags 

This code uses the static method RecoverOrCreate, which is a member of the Extended-WeakReference class, to create the reference. The method RecoverOrCreate does what the name implies. It tries to recover a previously saved weak reference, but if it does not find one, it will create one automatically and set the target reference to null.

There is a companion method, Recover, that will not create an instance if one is not found. You can use it if you are sure that the data is already present.

Setting Extended Weak Reference Properties

The parameters to the RecoverOrCreate method give it information about the particular data item in flash memory you want to reclaim. This information is in two parts: a type that uniquely identifies the data and an ID value. We created a marker type of our own for the flashlight application. You can use any suitable type.

 // Used in the call of RecoverOrCreate to uniquely identify our data private static class TypeUniqueToOurApp { } 

The class type and the ID value specify a particular item of persisted data. Note that the ID value makes it possible for you to group a number of items together by using the same ID value for each item. The final parameter to RecoverOrCreate is a flag value to be set in the newly created extended weak reference. There are two possible values for this flag, c_SurviveBoot and c_SurvivePowerdown. The former flag value would create a persistent store that would survive a "warm" restart. The latter, which is the one we want, creates data that will survive a power cycle.

We can also set the priority of the data on the end of the weak reference.

 useLogExtendedWeakReference.Priority =    (Int32)ExtendedWeakReference.PriorityLevel.Important; 

There are five levels of priority: OkayToThrowAway, NiceToHave, Important, Critical, and System. These allow the storage system to prioritize the use of nonvolatile memory. Remember that the amount of available memory may not be sufficient to store everything you want to persist, and so the storage may need to overwrite existing data to satisfy a request. The storage system uses the priority level to decide which items it will discard.

The storage system never discards references marked at System level, whereas it may discard items marked as OkayToThrowAway if it needs to store higher-priority items. The result of this will be that when a program makes an attempt to recover the target of such a weak reference, a discarded item will return null. In the case of our flashlight, we set the data to Important because we would like to have it around. If we have any very important information for a particular flashlight, perhaps a name to uniquely identify it and a date of manufacture, we would make that data System. You can have a range of different nonvolatile data items stored in a device, each of which might have a different level of priority.

Recovering Persisted Data from an Extended Weak Reference

Now that we have an extended weak reference instance, we can ask it to deliver the target.

 FlashlightUseLog flashlightLog = (FlashlightUseLog)useLogExtendedWeakReference.Target; 

The target property of the weak reference should refer to the FlashlightUseLog instance that we stored earlier, which the persistence mechanism will fetch for us. If there is a problem with recovering the item, or this is the first time that the program has used it, the program sets the reference to null.

 if (flashlightLog == null) {    Debug.Print("First time use, or data damaged");    flashlightLog = new FlashlightUseLog(); } 

The previous code prints a message and creates a new log instance if the extended weak reference has not delivered a result. Otherwise, we can go ahead and use the log; it will hold the data that it had when the program stored it. This mechanism provides a way of storing complex data items. The log itself contains a number of individual items. A single statement, setting the target of the extended weak reference, takes a snapshot of the log and all the data that it contains.

Note 

If you make any change to the design of the FlashlightLog or Flashlight-UseLog classes-for example, you add a new property "Light intensity" to the Flashligh-tUseLog class-the load of a previously serialized version of the class will fail. When you serialize a class, the program preserves the arrangement and names of the data properties with the data. If the class you are loading has a different configuration from that stored, the load will fail. This ensures the integrity of data, but it does mean that changes to your program may make it impossible to read stored data. In the case of the preceding example, in which the program and the embedded data are very tightly coupled together, this is not a problem. However, if you plan to release new versions of software that must read serialized configuration data from older code, you should give some attention to how the program will manage this situation.

Persistence and the Emulator

The .NET Micro Framework emulator is a very useful part of the development process. It runs on your PC and allows you to create and debug applications without needing to have actual hardware. However, the default emulator (that is, the one included with the .NET Micro Framework software development kit [SDK]) does not support extended weak references, which means that if you try to save data in the way described earlier, you will not be able to recover it. Fortunately, the sample programs included with the .NET Micro Framework contain a project that provides an example of the use of extended weak references. This includes a version of the emulator that stores persisted data to a file. The sample material for this book contains code for a data logging Flashlight which also uses this emulator to store information.




Embedded Programming with the Microsoft .Net Micro Framework
Embedded Programming with the Microsoft .NET Micro Framework
ISBN: 0735623651
EAN: 2147483647
Year: 2007
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net