EDN Admin
Well-known member
C9 Netduino Shield Series - Using Arduino Shields with a Netduino - Part II <h4>Introduction</h4> In our http://channel9.msdn.com/coding4fun/articles/What-Is-an-Arduino-Shield-and-Why-Should-My-Netduino-Care previous article , we examined what an Arduino shield is, how to build a simple custom shield and discussed how to quickly identify shields that are good candidates for a Netduino adaptation versus shields that may not be. In this article, we’ll take a https://www.adafruit.com/products/243 popular Arduino Logger Shield produced by Adafruit and we’ll interface it with a Netduino / Plus microcontroller http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B10%5D-2.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B6%5D-4.png" alt="image" width="240" height="188" border="0 The Arduino Logger Shield is an excellent one to start with because it offers immediate benefits to a Netduino / Plus user: Time-keeping SD card storage Two user-controllable LEDs A small prototyping area An onboard 3.3v voltage regulator for clean analog readings and power decoupling In our http://netduinohelpers.codeplex.com/SourceControl/list/changesets C# data logging application , well interact with the time keeper, the SD card storage and its card detect pin, the two LEDs as well as a temperature sensor (not included with the shield). Before diving into the details associated with the hardware, you may want to take a look at the C# objects representing the hardware: <pre class="brush: csharp
public static readonly string SdMountPoint = "SD";
public static OutputPort LedRed = new OutputPort(Pins.GPIO_PIN_D0, false);
public static OutputPort LedGreen = new OutputPort(Pins.GPIO_PIN_D1, false);
public static InputPort CardDetect = new InputPort(Pins.GPIO_PIN_D3, true, Port.ResistorMode.PullUp);
public static readonly Cpu.Pin ThermoCoupleChipSelect = Pins.GPIO_PIN_D2;
public static DS1307 Clock;
public static Max6675 ThermoCouple;
[/code] and their initialization: <pre class="brush: csharp
public static void InitializePeripherals() {
LedGreen.Write(true);
Clock = new DS1307();
ThermoCouple = new Max6675();
InitializeStorage(true);
InitializeClock(new DateTime(2012, 06, 14, 17, 00, 00));
ThermoCouple.Initialize(ThermoCoupleChipSelect);
TemperatureSampler = new Timer(new TimerCallback(LogTemperature), null, 250, TemperatureLoggerPeriod);
LedGreen.Write(false);
}
[/code] The SD card, represented by the SdMountPoint string, communicates with the application over SPI. The presence of the SD card in the reader is determined through the CardDetect input pin. The LEDs are simple outputs that well turn ON / OFF as the peripherals gets initialized and file I/Os take place with the SD card. The clock communicates with the application over the I2C protocol. The clocks most important functions are accessed through the Set() and Get() methods respectively used to set the time once and to get updated time stamps afterward. The thermocouple communicates over SPI with the application. It exposes a Read() method which caches a raw temperature sample accessed through the Celsius and Fahrenheit properties. Note: the Netduino Plus already features a built-in microSD card reader, in which case, having another one on the shield is not really needed. Except for this hardware difference, everything else discussed within this article applies equally to the regular Netduino and the Netduino Plus. <h4>Interfacing with the Arduino Logger shield’s hardware</h4> Adafruit is pretty good about making usable products and generally provides Arduino libraries to use with their hardware. Indeed, the Arduino Logger Shield is http://www.ladyada.net/make/logshield/index.html well documented and comes with two C++ libraries: https://github.com/adafruit/SD SD which implements a http://www.ladyada.net/make/logshield/sd.html FAT file system and supporting low-level SD card I/O functions . https://github.com/adafruit/RTClib RTCLib which wraps the I2C interface required to communicate with the http://www.ladyada.net/make/logshield/rtc.html DS1307 real time clock. <h5>The SD Card Interface</h5> Let’s deal with the SD card reader and the file system first: a quick review of https://github.com/adafruit/SD/blob/master/SD.h SD.h reveals two C++ classes: class File : public Stream {} exposing standard read, write, seek, flush file access functions. class SDClass {} exposing storage management such as file and directory operations. This is good news because the .NET Micro Framework on the Netduino already supports file streams and directory management through the use of the http://msdn.microsoft.com/en-us/library/hh400764 .NET MF System.IO assembly . This assembly comes with the http://www.netduino.com/downloads/MicroFrameworkSDK.msi .NET MF SDK port to the Netduino. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image004%5B3%5D-1.jpg <img title="clip_image004" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image004_thumb-1.jpg" alt="clip_image004" width="172" height="379" border="0 By the same token, interfacing with an SD card is provided by an assembly built by Secret Labs named SecretLabs.NETMF.IO which comes with the http://www.netduino.com/downloads/ Netduino SDK . http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B5%5D-6.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B3%5D-6.png" alt="image" width="640" height="114" border="0 SecretLabs.NETMF.IO provides two functions for mounting and un-mounting an SD card device and http://en.wikipedia.org/wiki/FAT_file_system the associated FAT file system so that it can be made usable by the .NET MF through assemblies such as http://msdn.microsoft.com/en-us/library/hh400764 System.IO. Its important to note that the SecretLabs.NETMF.IO assembly must not be deployed with an application targeting the http://www.netduino.com/netduinoplus/specs.htm Netduino Plus : on boot, the .NET Micro Framework implementation specific to the Netduino Plus automatically detects and mounts the SD card if one is present in its microSD card reader. This functionality is redundant with the MountSD / Unmount functions provided by the SecretLabs.NETMF.IO assembly which is only needed on Netduino SKUs without a built-in SD card reader. <h5>How does the .NET MF interact with the SD card through the shield?</h5> At this point, its a good time to review the Arduino Logger Shields pin-out and the http://www.ladyada.net/make/logshield/design.html shields schematics : http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B16%5D-2.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B10%5D-4.png" alt="image" width="472" height="422" border="0 As we know from our previous article, pins D10-D13 map to the SPI interface and pins A4-A5 map to the I2C interface of the Netduino. On the shields schematics, the SPI interface leads us to the SD & MMC section of the diagram, connected through a http://www.ladyada.net/wiki/partselector/ic?s%5b%5d=74ahc125n#logic 74HC125N logic-level shifter chip indicated as IC3A-D. The role of the logic-level shifter is to ensure that logic voltages supplied to the SD card do not exceed 3.3v, even if they come from a microcontroller using 5v logic levels, such as the Arduino. When using an SD card with a Netduino, a level-shifter is not required since all logic levels run at 3.3v on the AT91SAM7x chip but it doesnt interfere with any I/O operations either when the voltage is already 3.3v. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B20%5D-3.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B12%5D-4.png" alt="image" width="640" height="468" border="0 The SD card reader in itself is just a passive connector, giving access to the controller built into the SD card. It also provides a mechanical means (i.e. switches) of detecting the presence of a card in the reader (see JP14 pin 1) as well as detecting if the card is write-protected (see JP14 pin 2). Well make use of the card detection pin in the sample temperature logging application later on. For background on how SD cards work, the following application note http://alumni.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf "Secure Digital Card Interface for the MSP430" is excellent and much easier to digest than the extensive https://www.sdcard.org/downloads/pls/simplified_specs/ simplified SD card protocol specifications provided on the SD Card Association site . The following table taken from the http://alumni.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf "Secure Digital Card Interface for the MSP430" shows the pin out of an SD card and the corresponding SPI connections: http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image012%5B3%5D-1.jpg <img title="clip_image012" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image012_thumb-1.jpg" alt="clip_image012" width="285" height="247" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image014%5B3%5D-1.jpg <img title="clip_image014" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image014_thumb-1.jpg" alt="clip_image014" width="624" height="253" border="0 An SD standard-compliant card can support 3 distinct access modes, each one providing different performance characteristics: SD 1-bit protocol: synchronous serial protocol with one data line, one clock line and one line for commands. The full SD card protocol command set is supported in 1-bit mode. SD 4-bit protocol: this mode is nearly identical to the SD 1-bit mode, except that the data is multiplexed over 4 data lines, yielding up to 4x the performance of SD 1-bit mode. The full SD card protocol command set is supported in 4-bit mode. SPI mode: provide a standard SPI bus interface (/SS, MOSI, MISO, SCK). In SPI mode, the SD card only supports a subset of the full SD card protocol but it is sufficient for implementing a fully functional storage mechanism with a file system. As you might have guessed, the .NET Micro Framework on the Netduino http://netduino.com/downloads/netduinofirmware/netduinofirmware.zip makes use of the SD card in SPI mode (see DeviceCodeDriversBlockStorageSDSD_BL_driver.cpp). The block-oriented SD card I/Os are abstracted thanks to the FAT file system provided by the System.IO assembly (see DeviceCodeDriversFSFATFAT_FileHandle.cpp and FAT_LogicDisk.cpp). The role of the SecretLabs.NETMF.IO assembly on the Netduino (or its built-in equivalent on the Netduino Plus) is to initialize the SD card in SPI mode during the mounting process by sending the proper set of commands as defined in the https://www.sdcard.org/downloads/pls/simplified_specs/ SD Card protocol . In the http://netduinohelpers.codeplex.com/SourceControl/list/changesets C# code of the AdafruitNetduinoLogger sample application , which we will review as a whole later on in the code walkthrough section, the following function takes care of the SD card initialization: <pre class="brush: csharp
public static void InitializeStorage(bool mount) {
try {
if (mount == true) {
StorageDevice.MountSD(SdMountPoint, SPI.SPI_module.SPI1, Pins.GPIO_PIN_D10);
} else {
StorageDevice.Unmount(SdMountPoint);
}
} catch (Exception e) {
LogLine("InitializeStorage: " + e.Message);
SignalCriticalError();
}
}
[/code] Once mounted, the file system is accessed through System.IO calls such as this: <pre class="brush: csharp
using (var tempLogFile = new StreamWriter(filename, true)) {
tempLogFile.WriteLine(latestRecord);
tempLogFile.Flush();
}
[/code] Using the http://msdn.microsoft.com/en-us/library/hh423706 StreamWriter class in this context made sense for writing strings as used in the sample application: http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image016%5B3%5D-1.jpg <img title="clip_image016" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image016_thumb-1.jpg" alt="clip_image016" width="203" height="201" border="0 However, there are many other file I/O classes available in System.IO that may be better suited depending on the scenario. <h5>The DS1307 real time clock</h5> Our next step is to examine the interface with the DS1307 real time clock (RTC). Well start by extracting the most important parts of the http://datasheets.maxim-ic.com/en/ds/DS1307.pdf DS1307 datasheet and reviewing how its wired up on the shields schematics. <h5>DS1307 features</h5>Real-Time Clock (RTC) Counts Seconds, Minutes, Hours, Date of the Month, Month, Day of the week, and Year with Leap-Year Compensation Valid Up to 2100 56-Byte, Battery-Backed, General-Purpose RAM with Unlimited Writes I2C Serial Interface Programmable Square-Wave Output Signal Automatic Power-Fail Detect and Switch Circuitry Consumes Less than 500nA in Battery-Backup Mode with Oscillator Running Note: If you need to measure the time something takes in milliseconds, a time granularity that the DS1307 clock does not provide, you can use the http://msdn.microsoft.com/en-us/library/ee437011 Utility functions provided by the .NET Micro Framework like this: <pre class="brush: csharp
var tickStart = Utility.GetMachineTime().Ticks;
// <...code to be timed...>
var elapsedMs = (int)((Utility.GetMachineTime().Ticks - tickStart) / TimeSpan.TicksPerMillisecond);
[/code] This timing method relies on the CPUs internal tick counter and is not 100% accurate due to the overhead of the .NET MF itself but may be sufficient in most scenarios. In addition, the internal tick counter rolls over every so often, something that should be taken into account in production code. <h5>DS1307 register map</h5> Accessing the clocks features comes down reading and writing to and from a set of registers as described on page 8 of the datasheet. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image018%5B3%5D.jpg <img title="clip_image018" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image018_thumb.jpg" alt="clip_image018" width="624" height="263" border="0 http://datasheets.maxim-ic.com/en/ds/DS1307.pdf Page 9 of the DS1307 datasheet provides more details about the square wave generation function of the clock, which we will not be using here. The generated square wave signal is available on the shield through connector JP14 on pin 3 as you can see on the schematics below and can be used to provide a slow but reliable external clock signal to another device such as a microcontroller. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image020%5B3%5D.jpg <img title="clip_image020" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image020_thumb.jpg" alt="clip_image020" width="624" height="111" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image022%5B3%5D.jpg <img title="clip_image022" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image022_thumb.jpg" alt="clip_image022" width="435" height="382" border="0 <h5>DS1307 I2C bus address</h5> The final piece of the puzzle needed before we can use the DS1307 is the devices address on the I2C data bus and its maximum speed (specified at 100 KHz on page 10 of the datasheet). The device address is revealed on page 12 as being 1101000 binary (0x68) along with the two operations modes (Slave Receiver and Slave Transmitter) of the clock. The 8th bit of the address is used by the protocol to indicate whether a read or a write operation is requested. Note: I2C devices sometime make use of 10-bit addresses. If you arent familiar with the I2C data bus, you should read the section of the datasheet starting on page 10 which provides a good foundation for understanding how I2C generally works. It can be summarized as follows: I2C is a 2-wire serial protocol with one bidirectional data line referred to as SDA and one clock line, referred to as SCL. The I2C bus is an open-drain bus (i.e. devices pull the bus low to create a 0 and let go of the bus to create a 1). To achieve this, I2C requires a pull-up resistor on the SCL and SDA lines between 1.8K ohms and 10K ohms. I2C devices do not need to provide pull-ups themselves if the bus already has them. The I2C master (i.e. the Netduino microcontroller) always provides the clock signal, generally between 100 KHz (or lower) for standard speed devices or 400 KHz for high-speed devices. Theres also a http://www.i2c-bus.org/fast-mode-plus/ Fast Mode Plus allowing for speeds up to 1MHz on devices supporting it. There can be more than one master on the bus even though this is uncommon. An I2C device can have a 7-bit or 10-bit address, allowing for multiple I2C devices to be used on the same bus. I2C read and write operations are transactions initiated by the I2C master targeting a specific device by address. Some I2C slave devices can notify their master that they need to communicate using a bus interrupt. A transaction is framed by start and stop signals, with each byte transferred requiring an acknowledgement signal. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image9.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb2-1.png" alt="image" width="640" height="202" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image6%5B1%5D.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb1.png" alt="image" width="640" height="195" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image3.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb-4.png" alt="image" width="640" height="194" border="0 At this point, we have all the pieces needed to communicate with the RTC using I2C transactions. <h5>Using the I2C protocol with the .NET Micro Framework</h5> On the Arduino, the library used with the shield to communicate with the DS1307 is a https://github.com/adafruit/RTClib/blob/master/RTClib.cpp C++ library called RTClib . The https://github.com/adafruit/RTClib/blob/master/RTClib.h header of the library declares a DateTime class, similar in functionality to the standard http://msdn.microsoft.com/en-us/library/hh422126 .NET Micro Framework DateTime class provided by System in the mscorlib assembly. Well use the standard .NET MF data type to work with the clock instead. The next declared class is RTC_DS1307 which implements the driver for the DS1307 chip using the http://arduino.cc/it/Reference/Wire Wire library to wrap the I2C protocol . The .NET Micro Framework also http://msdn.microsoft.com/en-us/library/hh435266 supports the I2C protocol through to the http://msdn.microsoft.com/en-us/library/hh435127 Microsoft.SPOT.Hardware assembly . Here again, well use the .NET MF implementation of I2C in order to communicate with the clock. However, the I2C transaction patterns implemented by the C++ driver can still provide a useful guide for writing a C# driver for the DS1307 when you dont know where to begin just based on the datasheet. For instance, the following https://github.com/adafruit/RTClib/blob/master/RTClib.cpp functions taken from RTClib.cpp shows the call sequence used with the Wiring API to address the date and time registers of the clock: <pre class="brush: csharp
int i = 0; //The new wire library needs to take an int when you are sending for the zero register
void RTC_DS1307::adjust(const DateTime& dt) {
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(i);
Wire.write(bin2bcd(dt.second()));
Wire.write(bin2bcd(dt.minute()));
Wire.write(bin2bcd(dt.hour()));
Wire.write(bin2bcd(0));
Wire.write(bin2bcd(dt.day()));
Wire.write(bin2bcd(dt.month()));
Wire.write(bin2bcd(dt.year() - 2000));
Wire.write(i);
Wire.endTransmission();
}
DateTime RTC_DS1307::now() {
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(i);
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDRESS, 7);
uint8_t ss = bcd2bin(Wire.read() & 0x7F);
uint8_t mm = bcd2bin(Wire.read());
uint8_t hh = bcd2bin(Wire.read());
Wire.read();
uint8_t d = bcd2bin(Wire.read());
uint8_t m = bcd2bin(Wire.read());
uint16_t y = bcd2bin(Wire.read()) + 2000;
return DateTime (y, m, d, hh, mm, ss);
}
[/code] The final class is RTC_Millis, a utility class converting time data into milliseconds, effectively providing the functionality of the DateTime.Millisecond property on the .NET MF. Having assessed that the functionality of RTClib only handles date and time registers and knowing the role of the other clock registers, we can proceed with implementing a complete http://netduinohelpers.codeplex.com/SourceControl/list/changesets DS1307 C# driver , supporting the square wave and RAM functions, using the native I2C protocol support of the .NET Micro Framework. The driver starts by defining key constants matching the clock registers according to the datasheet: <pre class="brush: csharp
[Flags]
// Defines the frequency of the signal on the SQW interrupt pin on the clock when enabled
public enum SQWFreq { SQW_1Hz, SQW_4kHz, SQW_8kHz, SQW_32kHz, SQW_OFF };
[Flags]
// Defines the logic level on the SQW pin when the frequency is disabled
public enum SQWDisabledOutputControl { Zero, One };
// Real time clock I2C address
public const int DS1307_I2C_ADDRESS = 0x68;
// Start / End addresses of the date/time registers
public const byte DS1307_RTC_START_ADDRESS = 0x00;
public const byte DS1307_RTC_END_ADDRESS = 0x06;
// Start / End addresses of the user RAM registers
public const byte DS1307_RAM_START_ADDRESS = 0x08;
public const byte DS1307_RAM_END_ADDRESS = 0x3f;
// Square wave frequency generator register address
public const byte DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS = 0x07;
// Start / End addresses of the user RAM registers
public const byte DS1307_RAM_START_ADDRESS = 0x08;
public const byte DS1307_RAM_END_ADDRESS = 0x3f;
// Total size of the user RAM block
public const byte DS1307_RAM_SIZE = 56;
[/code] Next the driver defines an I2C device object representing the clock: <pre class="brush: csharp
// Instance of the I2C clock
protected I2CDevice Clock;
[/code] In the class constructor, the I2C clock device is initialized, specifying its address and speed in KHz: <pre class="brush: csharp
public DS1307(int timeoutMs = 30, int clockRateKHz = 50) {
TimeOutMs = timeoutMs;
ClockRateKHz = clockRateKHz;
Clock = new I2CDevice(new I2CDevice.Configuration(DS1307_I2C_ADDRESS, ClockRateKHz));
}
[/code] The driver retrieves the date and time from the clock through a Get function returning a DateTime object. <pre class="brush: csharp
public DateTime Get() {
byte[] clockData = new byte [7];
// Read time registers (7 bytes from DS1307_RTC_START_ADDRESS)
var transaction = new I2CDevice.I2CTransaction[] {
I2CDevice.CreateWriteTransaction(new byte[] {DS1307_RTC_START_ADDRESS}),
I2CDevice.CreateReadTransaction(clockData)
};
if (Clock.Execute(transaction, TimeOutMs) == 0) {
throw new Exception("I2C transaction failed");
}
return new DateTime(
BcdToDec(clockData[6]) + 2000, // year
BcdToDec(clockData[5]), // month
BcdToDec(clockData[4]), // day
BcdToDec(clockData[2] & 0x3f), // hours over 24 hours
BcdToDec(clockData[1]), // minutes
BcdToDec(clockData[0] & 0x7f) // seconds
);
}
[/code] Lets break it down: A 7-byte array is allocated which will receive the raw date and time data registers, starting at address DS1307_RTC_START_ADDRESS (0x00) and ending at DS1307_RTC_END_ADDRESS (0x06). An I2C transaction object is allocated, comprising two parameters: A write transaction object telling the DS1307 device which register address to start reading data from. In this case, this is DS1307_RTC_START_ADDRESS (0x00), the very first time-keeping register. A read transaction object specifying where the clocks time-keeping data registers will be stored, implicitly defining the total number of bytes to be read and acknowledged. Clock.Execute is the function calling into the .NET MF I2C interface to run the prepared transactions. The second parameter specifies a time out value expressed in milliseconds before the transaction fails, resulting in a generic exception being thrown. When the transactions succeed, a DateTime object is instantiated with the 7 time-keeping registers returned by the read transaction. Each register is converted from http://en.wikipedia.org/wiki/Binary_coded_decimal Binary Coded Decimal form to decimal form using a custom utility function: <pre class="brush: csharp
protected int BcdToDec(int val) {
return ((val / 16 * 10) + (val % 16));
}
[/code] Conversely, the driver provides a Set function to update the clocks time-keeping registers. Because the driver doesnt expect a response from the DS1307 in this scenario, the I2C transaction is write-only. The fields of the DateTime parameter corresponding to the time -keeping registers are converted from decimal form to http://en.wikipedia.org/wiki/Binary_coded_decimal BCD form and stuffed in a 7-byte array before executing the transaction. <pre class="brush: csharp
public void Set(DateTime dt) {
var transaction = new I2CDevice.I2CWriteTransaction[] {
I2CDevice.CreateWriteTransaction(new byte[] {
DS1307_RTC_START_ADDRESS,
DecToBcd(dt.Second),
DecToBcd(dt.Minute),
DecToBcd(dt.Hour),
DecToBcd((int)dt.DayOfWeek),
DecToBcd(dt.Day),
DecToBcd(dt.Month),
DecToBcd(dt.Year - 2000)} )
};
if (Clock.Execute(transaction, TimeOutMs) == 0) {
throw new Exception("I2C write transaction failed");
}
}
[/code] The rest of the functions provided by the C# driver implement the other DS1307 features, such as SetSquareWave Halt SetRAM GetRAM The [] operator used to access a specific clock register WriteRegister In all case, these functions are wrappers around the read and write I2C transaction model, involving the appropriate DS1307 registers as defined in the datasheet. <h4>Using the Adafruit Arduino Logger Shield as a temperature logger</h4> http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image15.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb6.png" alt="image" width="640" height="426" border="0 To illustrate the points discussed so far, well use the Adafruit Arduino Logger shield with a Netduino and a http://www.adafruit.com/products/269 MAX6675 thermocouple amplifier for the purpose of recording ambient temperature samples at ten second intervals. Each record includes a date, a time and the temperature expressed in Celsius and Fahrenheit. The records are written to daily files in CSV format for easy export to a spreadsheet, making the application easily adaptable for acquiring data from different sensors: <table width="302" border="0" cellspacing="0" cellpadding="0 <tbody><tr><td valign="bottom" width="73 Date </td><td valign="bottom" width="83 Time </td><td valign="bottom" width="62 Celsius </td><td valign="bottom" width="82 Fahrenheit </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:00:05 </td><td valign="bottom" width="62 18.75 </td><td valign="bottom" width="82 65.75 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:10:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:20:05 </td><td valign="bottom" width="62 18.5 </td><td valign="bottom" width="82 65.29 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:30:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:40:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:50:05 </td><td valign="bottom" width="62 18.75 </td><td valign="bottom" width="82 65.75 </td></tr></tbody></table> <h5>Device Connections</h5> Instead of permanently soldering the temperature sensor to the prototyping area of the shield, http://www.sparkfun.com/search/results?term=F%2FF&what=products female / female jumper wires were used to make connections between the shields own pin headers as well as the thermocouples male pin headers. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/LoggerShieldBoard3.png <img title="LoggerShieldBoard" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/LoggerShieldBoard_thumb.png" alt="LoggerShieldBoard" width="640" height="434" border="0 The following table enumerates these connections: <table width="515" border="0" cellspacing="0" cellpadding="0 <tbody><tr><td valign="top" width="243 Shield Pin </td><td valign="top" width="270 Destination Pin </td></tr><tr><td valign="top" width="244 3v (Power header) </td><td valign="top" width="270 Max6675 VCC </td></tr><tr><td valign="top" width="244 GND (Power or Digital I/O header) </td><td valign="top" width="270 Max6675 GND </td></tr><tr><td valign="top" width="244 D13 (Digital I/O header, SPI CLK) </td><td valign="top" width="270 Max6675 CLK (SPI CLK) </td></tr><tr><td valign="top" width="244 D12 (Digital I/O header, SPI MISO) </td><td valign="top" width="270 Max6675 DO (SPI MISO) </td></tr><tr><td valign="top" width="244 D2 (Digital I/O header, used as SPI /SS) </td><td valign="top" width="270 Max6675 CS (SPI /SS) </td></tr><tr><td valign="top" width="244 L1 (LEDS header) </td><td valign="top" width="270 D1 (Digital I/O header) </td></tr><tr><td valign="top" width="244 L2 (LEDS header) </td><td valign="top" width="270 D0 (Digital I/O header) </td></tr><tr><td valign="top" width="244 CD (SD card detect) </td><td valign="top" width="270 D3 (Digital I/O header) </td></tr></tbody></table><h5> </h5><h5>Reading temperature using an Adafruit Max6675 Thermocouple amplifier breakout board</h5> The http://datasheets.maxim-ic.com/en/ds/MAX6675.pdf Max6675 thermocouple amplifier chip on the breakout board is a read-only SPI device. When the CS pin (SPI /SS) of the device is asserted with a 1ms delay before reading, the chip returns a 12-bit value on its DO pin (SPI MISO) corresponding to the temperature measured by a http://en.wikipedia.org/wiki/Thermocouple K-type Thermocouple wire. The resulting http://netduinohelpers.codeplex.com/SourceControl/list/changesets C# driver for the Max6675 is short: <pre class="brush: csharp
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
namespace Maxim.Temperature{
public class Max6675 : IDisposable {
protected SPI Spi;
public void Initialize(Cpu.Pin chipSelect) {
Spi = new SPI(
new SPI.Configuration(
chipSelect, false, 1, 0, false, true, 2000, SPI.SPI_module.SPI1)
);
}
public double Celsius {
get { return RawSensorValue * 0.25; }
}
public double Farenheit {
get { return ((Celsius * 9.0) / 5.0) + 32; }
}
protected UInt16 RawSensorValue;
protected byte[] ReadBuffer = new byte[2];
protected byte[] WriteBuffer = new byte[2];
public void Read() {
RawSensorValue = 0;
Spi.WriteRead(WriteBuffer, ReadBuffer);
RawSensorValue |= ReadBuffer[0];
RawSensorValue <<= 8;
RawSensorValue |= ReadBuffer[1];
if ((RawSensorValue & 0x4) == 1) {
throw new ApplicationException("No thermocouple attached.");
}
RawSensorValue >>= 3;
}
public void Dispose() {
Spi.Dispose();
}
~Max6675() {
Dispose();
}
}
}
[/code] <h5>Temperature logger application walkthrough</h5> Lets review the key parts of the http://netduinohelpers.codeplex.com/SourceControl/list/changesets temperature logging application code and how it interacts with the devices connected to the shield. <pre class="brush: csharp
public static readonly string SdMountPoint = "SD";
[/code] Defines an arbitrary string used to refer to the SD card when using StorageDevice.MountSD and StorageDevice.Unmount functions. <pre class="brush: csharp
public static readonly int TemperatureLoggerPeriod = 10 * 1000; // milliseconds
[/code] Defines the interval between temperature samples. <pre class="brush: csharp
public static OutputPort LedRed = new OutputPort(Pins.GPIO_PIN_D0, false);
[/code] Defines an output connected to pin D0 controlling the state of the red LED on the shield. <pre class="brush: csharp
public static OutputPort LedGreen = new OutputPort(Pins.GPIO_PIN_D1, false);
[/code] Defines an output connected to pin D1 controlling the state of the green LED on the shield. <pre class="brush: csharp
public static InputPort CardDetect = new InputPort(
Pins.GPIO_PIN_D3,
true,
Port.ResistorMode.PullUp);
[/code] Defines an input connected to pin D3 used to determine if an SD card is inserted in the SD socket. <pre class="brush: csharp
public static ManualResetEvent ResetPeripherals = new ManualResetEvent(false);
[/code] Defines a manual reset event object that will be used in the main application loop to determine when to re-initialize the shields peripherals. <pre class="brush: csharp
public static readonly Cpu.Pin ThermoCoupleChipSelect = Pins.GPIO_PIN_D2;
[/code] Defines D2 as the SPI chip select pin connected to the Max6675 Thermocouple board. <pre class="brush: csharp
public static Timer TemperatureSampler;
[/code] Defines an instance of a timer object which will drive temperature sampling. <pre class="brush: csharp
public static DS1307 Clock;
[/code] Defines an instance of the DS1307 real time clock driver. <pre class="brush: csharp
public static Max6675 ThermoCouple;
[/code] Defines an instance of the Max6675 thermocouple driver. <pre class="brush: csharp
public static ArrayList Buffer = new ArrayList();
[/code] Defines an array list instance which will be used as a temporary buffer when the SD card is removed from its socket. The applications main loop is only concerned about the state of the peripherals: It initializes the devices connected to the shield It waits indefinitely for a signal indicating that a hardware error occurred It disposes of the current device instances and starts over <pre class="brush: csharp
public static void Main() {
while (true) {
InitializePeripherals();
ResetPeripherals.WaitOne();
ResetPeripherals.Reset();
DeInitializePeripherals();
}
}
[/code] InitializePeripherals indicates that it is working by controlling the green LED on the shield. Its role is focused on object creation and initialization. <pre class="brush: csharp
public static void InitializePeripherals() {
LedGreen.Write(true);
Clock = new DS1307();
ThermoCouple = new Max6675();
InitializeStorage(true);
InitializeClock(new DateTime(2012, 06, 14, 17, 00, 00));
ThermoCouple.Initialize(ThermoCoupleChipSelect);
TemperatureSampler = new Timer(
new TimerCallback(LogTemperature),
null,
250,
TemperatureLoggerPeriod);
LedGreen.Write(false);
}
[/code] If the initialization of a peripheral fails, the shield will quickly blink its LEDs, indefinitely: <pre class="brush: csharp
public static void SignalCriticalError() {
while (true) {
LedRed.Write(true);
LedGreen.Write(true);
Thread.Sleep(100);
LedRed.Write(false);
LedGreen.Write(false);
Thread.Sleep(100);
}
}
[/code] The clock initialization function only sets the clock date and time when it is unable to find a file named clockSet.txt on the SD card, ensuring that the initialization of the DS1307 only happens once in the InitializePeripherals function or until the file is deleted. <pre class="brush: csharp
public static void InitializeClock(DateTime dateTime) {
var clockSetIndicator = SdMountPoint + @"clockSet.txt";
try {
if (File.Exists(clockSetIndicator) == false) {
Clock.Set(dateTime);
Clock.Halt(false);
File.Create(clockSetIndicator);
}
} catch (Exception e) {
LogLine("InitializeClock: " + e.Message);
SignalCriticalError();
}
}
[/code] The LogTemperature function is the callback invoked by the Timer object every 10 seconds. The function indicates that it is working by turning the red LED on the shield ON and OFF. <pre class="brush: csharp
public static void LogTemperature(object obj) {
LedRed.Write(true);
}
[/code] The function reads the current time from the clock with Clock.Get() and takes a temperature sample with ThermoCouple.Read(). <pre class="brush: csharp
var tickStart = Utility.GetMachineTime().Ticks;
var now = Clock.Get();
ThermoCouple.Read();
var elapsedMs = (int)((Utility.GetMachineTime().Ticks - tickStart) / TimeSpan.TicksPerMillisecond);
[/code] Then, it concatenates a string containing the date, time and temperature expressed in Celsius and Fahrenheit, with each field separated by commas. <pre class="brush: csharp
var date = AddZeroPrefix(now.Year) + "/" + AddZeroPrefix(now.Month) + "/" + AddZeroPrefix(now.Day);
var time = AddZeroPrefix(now.Hour) + ":" + AddZeroPrefix(now.Minute) + ":" + AddZeroPrefix(now.Second) + ":" + AddZeroPrefix(elapsedMs);
var celsius = Shorten(ThermoCouple.Celsius.ToString());
var farenheit = Shorten(ThermoCouple.Farenheit.ToString());
var latestRecord = date + "," + time + "," + celsius + "," + farenheit;
[/code] To make the data more manageable, daily temperature files are created as needed, each one starting with the column headers expected for parsing the values in CSV format. <pre class="brush: csharp
var filename = SdMountPoint + BuildTemperatureLogFilename(now);
if (File.Exists(filename) == false) {
using (var tempLogFile = new StreamWriter(filename, true)) {
tempLogFile.WriteLine("date,time,celsius,fahrenheit");
}
}
[/code] The temperature sampling application lets the user remove the SD card from its socket so that the CSV files can be moved over to a PC for processing without losing data in the meantime. In order to do this, the application checks the state of the Card Detect pin before attempting file system I/Os. When the SD card is not present, the latest temperature record is preserved in the array list buffer until the SD card is put back in its socket. The array list data is then flushed to storage. <pre class="brush: csharp
if (CardDetect.Read() == false) {
using (var tempLogFile = new StreamWriter(filename, true)) {
if (Buffer.Count != 0) {
foreach (var bufferedLine in Buffer) {
tempLogFile.WriteLine(bufferedLine);
}
Buffer.Clear();
}
tempLogFile.WriteLine(latestRecord);
tempLogFile.Flush();
}
} else {
LogLine("No card in reader. Buffering record.");
Buffer.Add(latestRecord);
}
[/code] The temperature logging function expects to run out of memory if the array list buffer grows too large, in which case, all the records get purged. Other memory management strategies could be used to mitigate data loss in this case. However, this depends entirely on the requirements of the data logging application and is out of scope for this discussion. <pre class="brush: csharp
catch (OutOfMemoryException e) {
LogLine("Memory full. Clearing buffer.");
Buffer.Clear();
}
[/code] The temperature logging function also handles file system exceptions caused by the removal of the SD card and reacts by signaling the ResetPeripherals event. In turn, this lets the applications main loop know that the peripherals, and most specifically the SD card, need to be recycled and initialized again in order to recover from the error. <pre class="brush: csharp
catch (IOException e) {
LogLine("IO error. Resetting peripherals.");
Buffer.Add(latestRecord);
ResetPeripherals.Set();
}
[/code] <h5>Conclusion</h5> In this article, we took a shield designed for the Arduino and learned how to critically review the Arduino code libraries supporting it, drawing parallels with features offered by the .NET Micro Framework. This process allowed us to identify areas in the Arduino code which were not necessary to port over to C# such as SD card and file system handlers. It also allowed us to see the similarities in the way the Arduino and the Netduino handle I2C communications. Most importantly, we also learned the importance of reviewing a devices schematics and component datasheets to ensure that important features have not been omitted and potentially incorrectly implemented when considering using an unknown library: in the case of RTClib, we saw that the implementation was limited to the basic date and time functions of the DS1307, leaving out other useful features such as the clocks built-in RAM and the square wave generation functions. In our next article, well take on a much more complex shield and we will learn how to analyze Arduino libraries in depth before porting them from C/C++ to C#. <h6>Bio</h6> Fabien is the Chief Hacker and co-founder of Nwazet, a start-up company located in Redmond WA, specializing in Open Source software and embedded hardware design. Fabiens passion for technology started 30 years ago, creating video games for fun and for profit. He went on working on mainframes, industrial manufacturing systems, mobile and web applications. Before Nwazet, Fabien worked at MSFT for eight years in Windows Core Security, Windows Core Networking and Xbox. During downtime, Fabien enjoys shooting zombies and watching sci-fi. <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:21e6180e991b4430b459a0b00157f766
View the full article
public static readonly string SdMountPoint = "SD";
public static OutputPort LedRed = new OutputPort(Pins.GPIO_PIN_D0, false);
public static OutputPort LedGreen = new OutputPort(Pins.GPIO_PIN_D1, false);
public static InputPort CardDetect = new InputPort(Pins.GPIO_PIN_D3, true, Port.ResistorMode.PullUp);
public static readonly Cpu.Pin ThermoCoupleChipSelect = Pins.GPIO_PIN_D2;
public static DS1307 Clock;
public static Max6675 ThermoCouple;
[/code] and their initialization: <pre class="brush: csharp
public static void InitializePeripherals() {
LedGreen.Write(true);
Clock = new DS1307();
ThermoCouple = new Max6675();
InitializeStorage(true);
InitializeClock(new DateTime(2012, 06, 14, 17, 00, 00));
ThermoCouple.Initialize(ThermoCoupleChipSelect);
TemperatureSampler = new Timer(new TimerCallback(LogTemperature), null, 250, TemperatureLoggerPeriod);
LedGreen.Write(false);
}
[/code] The SD card, represented by the SdMountPoint string, communicates with the application over SPI. The presence of the SD card in the reader is determined through the CardDetect input pin. The LEDs are simple outputs that well turn ON / OFF as the peripherals gets initialized and file I/Os take place with the SD card. The clock communicates with the application over the I2C protocol. The clocks most important functions are accessed through the Set() and Get() methods respectively used to set the time once and to get updated time stamps afterward. The thermocouple communicates over SPI with the application. It exposes a Read() method which caches a raw temperature sample accessed through the Celsius and Fahrenheit properties. Note: the Netduino Plus already features a built-in microSD card reader, in which case, having another one on the shield is not really needed. Except for this hardware difference, everything else discussed within this article applies equally to the regular Netduino and the Netduino Plus. <h4>Interfacing with the Arduino Logger shield’s hardware</h4> Adafruit is pretty good about making usable products and generally provides Arduino libraries to use with their hardware. Indeed, the Arduino Logger Shield is http://www.ladyada.net/make/logshield/index.html well documented and comes with two C++ libraries: https://github.com/adafruit/SD SD which implements a http://www.ladyada.net/make/logshield/sd.html FAT file system and supporting low-level SD card I/O functions . https://github.com/adafruit/RTClib RTCLib which wraps the I2C interface required to communicate with the http://www.ladyada.net/make/logshield/rtc.html DS1307 real time clock. <h5>The SD Card Interface</h5> Let’s deal with the SD card reader and the file system first: a quick review of https://github.com/adafruit/SD/blob/master/SD.h SD.h reveals two C++ classes: class File : public Stream {} exposing standard read, write, seek, flush file access functions. class SDClass {} exposing storage management such as file and directory operations. This is good news because the .NET Micro Framework on the Netduino already supports file streams and directory management through the use of the http://msdn.microsoft.com/en-us/library/hh400764 .NET MF System.IO assembly . This assembly comes with the http://www.netduino.com/downloads/MicroFrameworkSDK.msi .NET MF SDK port to the Netduino. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image004%5B3%5D-1.jpg <img title="clip_image004" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image004_thumb-1.jpg" alt="clip_image004" width="172" height="379" border="0 By the same token, interfacing with an SD card is provided by an assembly built by Secret Labs named SecretLabs.NETMF.IO which comes with the http://www.netduino.com/downloads/ Netduino SDK . http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B5%5D-6.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B3%5D-6.png" alt="image" width="640" height="114" border="0 SecretLabs.NETMF.IO provides two functions for mounting and un-mounting an SD card device and http://en.wikipedia.org/wiki/FAT_file_system the associated FAT file system so that it can be made usable by the .NET MF through assemblies such as http://msdn.microsoft.com/en-us/library/hh400764 System.IO. Its important to note that the SecretLabs.NETMF.IO assembly must not be deployed with an application targeting the http://www.netduino.com/netduinoplus/specs.htm Netduino Plus : on boot, the .NET Micro Framework implementation specific to the Netduino Plus automatically detects and mounts the SD card if one is present in its microSD card reader. This functionality is redundant with the MountSD / Unmount functions provided by the SecretLabs.NETMF.IO assembly which is only needed on Netduino SKUs without a built-in SD card reader. <h5>How does the .NET MF interact with the SD card through the shield?</h5> At this point, its a good time to review the Arduino Logger Shields pin-out and the http://www.ladyada.net/make/logshield/design.html shields schematics : http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B16%5D-2.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B10%5D-4.png" alt="image" width="472" height="422" border="0 As we know from our previous article, pins D10-D13 map to the SPI interface and pins A4-A5 map to the I2C interface of the Netduino. On the shields schematics, the SPI interface leads us to the SD & MMC section of the diagram, connected through a http://www.ladyada.net/wiki/partselector/ic?s%5b%5d=74ahc125n#logic 74HC125N logic-level shifter chip indicated as IC3A-D. The role of the logic-level shifter is to ensure that logic voltages supplied to the SD card do not exceed 3.3v, even if they come from a microcontroller using 5v logic levels, such as the Arduino. When using an SD card with a Netduino, a level-shifter is not required since all logic levels run at 3.3v on the AT91SAM7x chip but it doesnt interfere with any I/O operations either when the voltage is already 3.3v. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image%5B20%5D-3.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb%5B12%5D-4.png" alt="image" width="640" height="468" border="0 The SD card reader in itself is just a passive connector, giving access to the controller built into the SD card. It also provides a mechanical means (i.e. switches) of detecting the presence of a card in the reader (see JP14 pin 1) as well as detecting if the card is write-protected (see JP14 pin 2). Well make use of the card detection pin in the sample temperature logging application later on. For background on how SD cards work, the following application note http://alumni.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf "Secure Digital Card Interface for the MSP430" is excellent and much easier to digest than the extensive https://www.sdcard.org/downloads/pls/simplified_specs/ simplified SD card protocol specifications provided on the SD Card Association site . The following table taken from the http://alumni.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf "Secure Digital Card Interface for the MSP430" shows the pin out of an SD card and the corresponding SPI connections: http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image012%5B3%5D-1.jpg <img title="clip_image012" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image012_thumb-1.jpg" alt="clip_image012" width="285" height="247" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image014%5B3%5D-1.jpg <img title="clip_image014" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image014_thumb-1.jpg" alt="clip_image014" width="624" height="253" border="0 An SD standard-compliant card can support 3 distinct access modes, each one providing different performance characteristics: SD 1-bit protocol: synchronous serial protocol with one data line, one clock line and one line for commands. The full SD card protocol command set is supported in 1-bit mode. SD 4-bit protocol: this mode is nearly identical to the SD 1-bit mode, except that the data is multiplexed over 4 data lines, yielding up to 4x the performance of SD 1-bit mode. The full SD card protocol command set is supported in 4-bit mode. SPI mode: provide a standard SPI bus interface (/SS, MOSI, MISO, SCK). In SPI mode, the SD card only supports a subset of the full SD card protocol but it is sufficient for implementing a fully functional storage mechanism with a file system. As you might have guessed, the .NET Micro Framework on the Netduino http://netduino.com/downloads/netduinofirmware/netduinofirmware.zip makes use of the SD card in SPI mode (see DeviceCodeDriversBlockStorageSDSD_BL_driver.cpp). The block-oriented SD card I/Os are abstracted thanks to the FAT file system provided by the System.IO assembly (see DeviceCodeDriversFSFATFAT_FileHandle.cpp and FAT_LogicDisk.cpp). The role of the SecretLabs.NETMF.IO assembly on the Netduino (or its built-in equivalent on the Netduino Plus) is to initialize the SD card in SPI mode during the mounting process by sending the proper set of commands as defined in the https://www.sdcard.org/downloads/pls/simplified_specs/ SD Card protocol . In the http://netduinohelpers.codeplex.com/SourceControl/list/changesets C# code of the AdafruitNetduinoLogger sample application , which we will review as a whole later on in the code walkthrough section, the following function takes care of the SD card initialization: <pre class="brush: csharp
public static void InitializeStorage(bool mount) {
try {
if (mount == true) {
StorageDevice.MountSD(SdMountPoint, SPI.SPI_module.SPI1, Pins.GPIO_PIN_D10);
} else {
StorageDevice.Unmount(SdMountPoint);
}
} catch (Exception e) {
LogLine("InitializeStorage: " + e.Message);
SignalCriticalError();
}
}
[/code] Once mounted, the file system is accessed through System.IO calls such as this: <pre class="brush: csharp
using (var tempLogFile = new StreamWriter(filename, true)) {
tempLogFile.WriteLine(latestRecord);
tempLogFile.Flush();
}
[/code] Using the http://msdn.microsoft.com/en-us/library/hh423706 StreamWriter class in this context made sense for writing strings as used in the sample application: http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image016%5B3%5D-1.jpg <img title="clip_image016" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image016_thumb-1.jpg" alt="clip_image016" width="203" height="201" border="0 However, there are many other file I/O classes available in System.IO that may be better suited depending on the scenario. <h5>The DS1307 real time clock</h5> Our next step is to examine the interface with the DS1307 real time clock (RTC). Well start by extracting the most important parts of the http://datasheets.maxim-ic.com/en/ds/DS1307.pdf DS1307 datasheet and reviewing how its wired up on the shields schematics. <h5>DS1307 features</h5>Real-Time Clock (RTC) Counts Seconds, Minutes, Hours, Date of the Month, Month, Day of the week, and Year with Leap-Year Compensation Valid Up to 2100 56-Byte, Battery-Backed, General-Purpose RAM with Unlimited Writes I2C Serial Interface Programmable Square-Wave Output Signal Automatic Power-Fail Detect and Switch Circuitry Consumes Less than 500nA in Battery-Backup Mode with Oscillator Running Note: If you need to measure the time something takes in milliseconds, a time granularity that the DS1307 clock does not provide, you can use the http://msdn.microsoft.com/en-us/library/ee437011 Utility functions provided by the .NET Micro Framework like this: <pre class="brush: csharp
var tickStart = Utility.GetMachineTime().Ticks;
// <...code to be timed...>
var elapsedMs = (int)((Utility.GetMachineTime().Ticks - tickStart) / TimeSpan.TicksPerMillisecond);
[/code] This timing method relies on the CPUs internal tick counter and is not 100% accurate due to the overhead of the .NET MF itself but may be sufficient in most scenarios. In addition, the internal tick counter rolls over every so often, something that should be taken into account in production code. <h5>DS1307 register map</h5> Accessing the clocks features comes down reading and writing to and from a set of registers as described on page 8 of the datasheet. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image018%5B3%5D.jpg <img title="clip_image018" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image018_thumb.jpg" alt="clip_image018" width="624" height="263" border="0 http://datasheets.maxim-ic.com/en/ds/DS1307.pdf Page 9 of the DS1307 datasheet provides more details about the square wave generation function of the clock, which we will not be using here. The generated square wave signal is available on the shield through connector JP14 on pin 3 as you can see on the schematics below and can be used to provide a slow but reliable external clock signal to another device such as a microcontroller. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image020%5B3%5D.jpg <img title="clip_image020" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image020_thumb.jpg" alt="clip_image020" width="624" height="111" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image022%5B3%5D.jpg <img title="clip_image022" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/clip_image022_thumb.jpg" alt="clip_image022" width="435" height="382" border="0 <h5>DS1307 I2C bus address</h5> The final piece of the puzzle needed before we can use the DS1307 is the devices address on the I2C data bus and its maximum speed (specified at 100 KHz on page 10 of the datasheet). The device address is revealed on page 12 as being 1101000 binary (0x68) along with the two operations modes (Slave Receiver and Slave Transmitter) of the clock. The 8th bit of the address is used by the protocol to indicate whether a read or a write operation is requested. Note: I2C devices sometime make use of 10-bit addresses. If you arent familiar with the I2C data bus, you should read the section of the datasheet starting on page 10 which provides a good foundation for understanding how I2C generally works. It can be summarized as follows: I2C is a 2-wire serial protocol with one bidirectional data line referred to as SDA and one clock line, referred to as SCL. The I2C bus is an open-drain bus (i.e. devices pull the bus low to create a 0 and let go of the bus to create a 1). To achieve this, I2C requires a pull-up resistor on the SCL and SDA lines between 1.8K ohms and 10K ohms. I2C devices do not need to provide pull-ups themselves if the bus already has them. The I2C master (i.e. the Netduino microcontroller) always provides the clock signal, generally between 100 KHz (or lower) for standard speed devices or 400 KHz for high-speed devices. Theres also a http://www.i2c-bus.org/fast-mode-plus/ Fast Mode Plus allowing for speeds up to 1MHz on devices supporting it. There can be more than one master on the bus even though this is uncommon. An I2C device can have a 7-bit or 10-bit address, allowing for multiple I2C devices to be used on the same bus. I2C read and write operations are transactions initiated by the I2C master targeting a specific device by address. Some I2C slave devices can notify their master that they need to communicate using a bus interrupt. A transaction is framed by start and stop signals, with each byte transferred requiring an acknowledgement signal. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image9.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb2-1.png" alt="image" width="640" height="202" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image6%5B1%5D.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb1.png" alt="image" width="640" height="195" border="0 http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image3.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb-4.png" alt="image" width="640" height="194" border="0 At this point, we have all the pieces needed to communicate with the RTC using I2C transactions. <h5>Using the I2C protocol with the .NET Micro Framework</h5> On the Arduino, the library used with the shield to communicate with the DS1307 is a https://github.com/adafruit/RTClib/blob/master/RTClib.cpp C++ library called RTClib . The https://github.com/adafruit/RTClib/blob/master/RTClib.h header of the library declares a DateTime class, similar in functionality to the standard http://msdn.microsoft.com/en-us/library/hh422126 .NET Micro Framework DateTime class provided by System in the mscorlib assembly. Well use the standard .NET MF data type to work with the clock instead. The next declared class is RTC_DS1307 which implements the driver for the DS1307 chip using the http://arduino.cc/it/Reference/Wire Wire library to wrap the I2C protocol . The .NET Micro Framework also http://msdn.microsoft.com/en-us/library/hh435266 supports the I2C protocol through to the http://msdn.microsoft.com/en-us/library/hh435127 Microsoft.SPOT.Hardware assembly . Here again, well use the .NET MF implementation of I2C in order to communicate with the clock. However, the I2C transaction patterns implemented by the C++ driver can still provide a useful guide for writing a C# driver for the DS1307 when you dont know where to begin just based on the datasheet. For instance, the following https://github.com/adafruit/RTClib/blob/master/RTClib.cpp functions taken from RTClib.cpp shows the call sequence used with the Wiring API to address the date and time registers of the clock: <pre class="brush: csharp
int i = 0; //The new wire library needs to take an int when you are sending for the zero register
void RTC_DS1307::adjust(const DateTime& dt) {
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(i);
Wire.write(bin2bcd(dt.second()));
Wire.write(bin2bcd(dt.minute()));
Wire.write(bin2bcd(dt.hour()));
Wire.write(bin2bcd(0));
Wire.write(bin2bcd(dt.day()));
Wire.write(bin2bcd(dt.month()));
Wire.write(bin2bcd(dt.year() - 2000));
Wire.write(i);
Wire.endTransmission();
}
DateTime RTC_DS1307::now() {
Wire.beginTransmission(DS1307_ADDRESS);
Wire.write(i);
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDRESS, 7);
uint8_t ss = bcd2bin(Wire.read() & 0x7F);
uint8_t mm = bcd2bin(Wire.read());
uint8_t hh = bcd2bin(Wire.read());
Wire.read();
uint8_t d = bcd2bin(Wire.read());
uint8_t m = bcd2bin(Wire.read());
uint16_t y = bcd2bin(Wire.read()) + 2000;
return DateTime (y, m, d, hh, mm, ss);
}
[/code] The final class is RTC_Millis, a utility class converting time data into milliseconds, effectively providing the functionality of the DateTime.Millisecond property on the .NET MF. Having assessed that the functionality of RTClib only handles date and time registers and knowing the role of the other clock registers, we can proceed with implementing a complete http://netduinohelpers.codeplex.com/SourceControl/list/changesets DS1307 C# driver , supporting the square wave and RAM functions, using the native I2C protocol support of the .NET Micro Framework. The driver starts by defining key constants matching the clock registers according to the datasheet: <pre class="brush: csharp
[Flags]
// Defines the frequency of the signal on the SQW interrupt pin on the clock when enabled
public enum SQWFreq { SQW_1Hz, SQW_4kHz, SQW_8kHz, SQW_32kHz, SQW_OFF };
[Flags]
// Defines the logic level on the SQW pin when the frequency is disabled
public enum SQWDisabledOutputControl { Zero, One };
// Real time clock I2C address
public const int DS1307_I2C_ADDRESS = 0x68;
// Start / End addresses of the date/time registers
public const byte DS1307_RTC_START_ADDRESS = 0x00;
public const byte DS1307_RTC_END_ADDRESS = 0x06;
// Start / End addresses of the user RAM registers
public const byte DS1307_RAM_START_ADDRESS = 0x08;
public const byte DS1307_RAM_END_ADDRESS = 0x3f;
// Square wave frequency generator register address
public const byte DS1307_SQUARE_WAVE_CTRL_REGISTER_ADDRESS = 0x07;
// Start / End addresses of the user RAM registers
public const byte DS1307_RAM_START_ADDRESS = 0x08;
public const byte DS1307_RAM_END_ADDRESS = 0x3f;
// Total size of the user RAM block
public const byte DS1307_RAM_SIZE = 56;
[/code] Next the driver defines an I2C device object representing the clock: <pre class="brush: csharp
// Instance of the I2C clock
protected I2CDevice Clock;
[/code] In the class constructor, the I2C clock device is initialized, specifying its address and speed in KHz: <pre class="brush: csharp
public DS1307(int timeoutMs = 30, int clockRateKHz = 50) {
TimeOutMs = timeoutMs;
ClockRateKHz = clockRateKHz;
Clock = new I2CDevice(new I2CDevice.Configuration(DS1307_I2C_ADDRESS, ClockRateKHz));
}
[/code] The driver retrieves the date and time from the clock through a Get function returning a DateTime object. <pre class="brush: csharp
public DateTime Get() {
byte[] clockData = new byte [7];
// Read time registers (7 bytes from DS1307_RTC_START_ADDRESS)
var transaction = new I2CDevice.I2CTransaction[] {
I2CDevice.CreateWriteTransaction(new byte[] {DS1307_RTC_START_ADDRESS}),
I2CDevice.CreateReadTransaction(clockData)
};
if (Clock.Execute(transaction, TimeOutMs) == 0) {
throw new Exception("I2C transaction failed");
}
return new DateTime(
BcdToDec(clockData[6]) + 2000, // year
BcdToDec(clockData[5]), // month
BcdToDec(clockData[4]), // day
BcdToDec(clockData[2] & 0x3f), // hours over 24 hours
BcdToDec(clockData[1]), // minutes
BcdToDec(clockData[0] & 0x7f) // seconds
);
}
[/code] Lets break it down: A 7-byte array is allocated which will receive the raw date and time data registers, starting at address DS1307_RTC_START_ADDRESS (0x00) and ending at DS1307_RTC_END_ADDRESS (0x06). An I2C transaction object is allocated, comprising two parameters: A write transaction object telling the DS1307 device which register address to start reading data from. In this case, this is DS1307_RTC_START_ADDRESS (0x00), the very first time-keeping register. A read transaction object specifying where the clocks time-keeping data registers will be stored, implicitly defining the total number of bytes to be read and acknowledged. Clock.Execute is the function calling into the .NET MF I2C interface to run the prepared transactions. The second parameter specifies a time out value expressed in milliseconds before the transaction fails, resulting in a generic exception being thrown. When the transactions succeed, a DateTime object is instantiated with the 7 time-keeping registers returned by the read transaction. Each register is converted from http://en.wikipedia.org/wiki/Binary_coded_decimal Binary Coded Decimal form to decimal form using a custom utility function: <pre class="brush: csharp
protected int BcdToDec(int val) {
return ((val / 16 * 10) + (val % 16));
}
[/code] Conversely, the driver provides a Set function to update the clocks time-keeping registers. Because the driver doesnt expect a response from the DS1307 in this scenario, the I2C transaction is write-only. The fields of the DateTime parameter corresponding to the time -keeping registers are converted from decimal form to http://en.wikipedia.org/wiki/Binary_coded_decimal BCD form and stuffed in a 7-byte array before executing the transaction. <pre class="brush: csharp
public void Set(DateTime dt) {
var transaction = new I2CDevice.I2CWriteTransaction[] {
I2CDevice.CreateWriteTransaction(new byte[] {
DS1307_RTC_START_ADDRESS,
DecToBcd(dt.Second),
DecToBcd(dt.Minute),
DecToBcd(dt.Hour),
DecToBcd((int)dt.DayOfWeek),
DecToBcd(dt.Day),
DecToBcd(dt.Month),
DecToBcd(dt.Year - 2000)} )
};
if (Clock.Execute(transaction, TimeOutMs) == 0) {
throw new Exception("I2C write transaction failed");
}
}
[/code] The rest of the functions provided by the C# driver implement the other DS1307 features, such as SetSquareWave Halt SetRAM GetRAM The [] operator used to access a specific clock register WriteRegister In all case, these functions are wrappers around the read and write I2C transaction model, involving the appropriate DS1307 registers as defined in the datasheet. <h4>Using the Adafruit Arduino Logger Shield as a temperature logger</h4> http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image15.png <img title="image" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/image_thumb6.png" alt="image" width="640" height="426" border="0 To illustrate the points discussed so far, well use the Adafruit Arduino Logger shield with a Netduino and a http://www.adafruit.com/products/269 MAX6675 thermocouple amplifier for the purpose of recording ambient temperature samples at ten second intervals. Each record includes a date, a time and the temperature expressed in Celsius and Fahrenheit. The records are written to daily files in CSV format for easy export to a spreadsheet, making the application easily adaptable for acquiring data from different sensors: <table width="302" border="0" cellspacing="0" cellpadding="0 <tbody><tr><td valign="bottom" width="73 Date </td><td valign="bottom" width="83 Time </td><td valign="bottom" width="62 Celsius </td><td valign="bottom" width="82 Fahrenheit </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:00:05 </td><td valign="bottom" width="62 18.75 </td><td valign="bottom" width="82 65.75 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:10:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:20:05 </td><td valign="bottom" width="62 18.5 </td><td valign="bottom" width="82 65.29 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:30:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:40:05 </td><td valign="bottom" width="62 18 </td><td valign="bottom" width="82 64.4 </td></tr><tr><td valign="bottom" width="73 6/14/2012 </td><td valign="bottom" width="83 15:35:50:05 </td><td valign="bottom" width="62 18.75 </td><td valign="bottom" width="82 65.75 </td></tr></tbody></table> <h5>Device Connections</h5> Instead of permanently soldering the temperature sensor to the prototyping area of the shield, http://www.sparkfun.com/search/results?term=F%2FF&what=products female / female jumper wires were used to make connections between the shields own pin headers as well as the thermocouples male pin headers. http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/LoggerShieldBoard3.png <img title="LoggerShieldBoard" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/LoggerShieldBoard_thumb.png" alt="LoggerShieldBoard" width="640" height="434" border="0 The following table enumerates these connections: <table width="515" border="0" cellspacing="0" cellpadding="0 <tbody><tr><td valign="top" width="243 Shield Pin </td><td valign="top" width="270 Destination Pin </td></tr><tr><td valign="top" width="244 3v (Power header) </td><td valign="top" width="270 Max6675 VCC </td></tr><tr><td valign="top" width="244 GND (Power or Digital I/O header) </td><td valign="top" width="270 Max6675 GND </td></tr><tr><td valign="top" width="244 D13 (Digital I/O header, SPI CLK) </td><td valign="top" width="270 Max6675 CLK (SPI CLK) </td></tr><tr><td valign="top" width="244 D12 (Digital I/O header, SPI MISO) </td><td valign="top" width="270 Max6675 DO (SPI MISO) </td></tr><tr><td valign="top" width="244 D2 (Digital I/O header, used as SPI /SS) </td><td valign="top" width="270 Max6675 CS (SPI /SS) </td></tr><tr><td valign="top" width="244 L1 (LEDS header) </td><td valign="top" width="270 D1 (Digital I/O header) </td></tr><tr><td valign="top" width="244 L2 (LEDS header) </td><td valign="top" width="270 D0 (Digital I/O header) </td></tr><tr><td valign="top" width="244 CD (SD card detect) </td><td valign="top" width="270 D3 (Digital I/O header) </td></tr></tbody></table><h5> </h5><h5>Reading temperature using an Adafruit Max6675 Thermocouple amplifier breakout board</h5> The http://datasheets.maxim-ic.com/en/ds/MAX6675.pdf Max6675 thermocouple amplifier chip on the breakout board is a read-only SPI device. When the CS pin (SPI /SS) of the device is asserted with a 1ms delay before reading, the chip returns a 12-bit value on its DO pin (SPI MISO) corresponding to the temperature measured by a http://en.wikipedia.org/wiki/Thermocouple K-type Thermocouple wire. The resulting http://netduinohelpers.codeplex.com/SourceControl/list/changesets C# driver for the Max6675 is short: <pre class="brush: csharp
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
namespace Maxim.Temperature{
public class Max6675 : IDisposable {
protected SPI Spi;
public void Initialize(Cpu.Pin chipSelect) {
Spi = new SPI(
new SPI.Configuration(
chipSelect, false, 1, 0, false, true, 2000, SPI.SPI_module.SPI1)
);
}
public double Celsius {
get { return RawSensorValue * 0.25; }
}
public double Farenheit {
get { return ((Celsius * 9.0) / 5.0) + 32; }
}
protected UInt16 RawSensorValue;
protected byte[] ReadBuffer = new byte[2];
protected byte[] WriteBuffer = new byte[2];
public void Read() {
RawSensorValue = 0;
Spi.WriteRead(WriteBuffer, ReadBuffer);
RawSensorValue |= ReadBuffer[0];
RawSensorValue <<= 8;
RawSensorValue |= ReadBuffer[1];
if ((RawSensorValue & 0x4) == 1) {
throw new ApplicationException("No thermocouple attached.");
}
RawSensorValue >>= 3;
}
public void Dispose() {
Spi.Dispose();
}
~Max6675() {
Dispose();
}
}
}
[/code] <h5>Temperature logger application walkthrough</h5> Lets review the key parts of the http://netduinohelpers.codeplex.com/SourceControl/list/changesets temperature logging application code and how it interacts with the devices connected to the shield. <pre class="brush: csharp
public static readonly string SdMountPoint = "SD";
[/code] Defines an arbitrary string used to refer to the SD card when using StorageDevice.MountSD and StorageDevice.Unmount functions. <pre class="brush: csharp
public static readonly int TemperatureLoggerPeriod = 10 * 1000; // milliseconds
[/code] Defines the interval between temperature samples. <pre class="brush: csharp
public static OutputPort LedRed = new OutputPort(Pins.GPIO_PIN_D0, false);
[/code] Defines an output connected to pin D0 controlling the state of the red LED on the shield. <pre class="brush: csharp
public static OutputPort LedGreen = new OutputPort(Pins.GPIO_PIN_D1, false);
[/code] Defines an output connected to pin D1 controlling the state of the green LED on the shield. <pre class="brush: csharp
public static InputPort CardDetect = new InputPort(
Pins.GPIO_PIN_D3,
true,
Port.ResistorMode.PullUp);
[/code] Defines an input connected to pin D3 used to determine if an SD card is inserted in the SD socket. <pre class="brush: csharp
public static ManualResetEvent ResetPeripherals = new ManualResetEvent(false);
[/code] Defines a manual reset event object that will be used in the main application loop to determine when to re-initialize the shields peripherals. <pre class="brush: csharp
public static readonly Cpu.Pin ThermoCoupleChipSelect = Pins.GPIO_PIN_D2;
[/code] Defines D2 as the SPI chip select pin connected to the Max6675 Thermocouple board. <pre class="brush: csharp
public static Timer TemperatureSampler;
[/code] Defines an instance of a timer object which will drive temperature sampling. <pre class="brush: csharp
public static DS1307 Clock;
[/code] Defines an instance of the DS1307 real time clock driver. <pre class="brush: csharp
public static Max6675 ThermoCouple;
[/code] Defines an instance of the Max6675 thermocouple driver. <pre class="brush: csharp
public static ArrayList Buffer = new ArrayList();
[/code] Defines an array list instance which will be used as a temporary buffer when the SD card is removed from its socket. The applications main loop is only concerned about the state of the peripherals: It initializes the devices connected to the shield It waits indefinitely for a signal indicating that a hardware error occurred It disposes of the current device instances and starts over <pre class="brush: csharp
public static void Main() {
while (true) {
InitializePeripherals();
ResetPeripherals.WaitOne();
ResetPeripherals.Reset();
DeInitializePeripherals();
}
}
[/code] InitializePeripherals indicates that it is working by controlling the green LED on the shield. Its role is focused on object creation and initialization. <pre class="brush: csharp
public static void InitializePeripherals() {
LedGreen.Write(true);
Clock = new DS1307();
ThermoCouple = new Max6675();
InitializeStorage(true);
InitializeClock(new DateTime(2012, 06, 14, 17, 00, 00));
ThermoCouple.Initialize(ThermoCoupleChipSelect);
TemperatureSampler = new Timer(
new TimerCallback(LogTemperature),
null,
250,
TemperatureLoggerPeriod);
LedGreen.Write(false);
}
[/code] If the initialization of a peripheral fails, the shield will quickly blink its LEDs, indefinitely: <pre class="brush: csharp
public static void SignalCriticalError() {
while (true) {
LedRed.Write(true);
LedGreen.Write(true);
Thread.Sleep(100);
LedRed.Write(false);
LedGreen.Write(false);
Thread.Sleep(100);
}
}
[/code] The clock initialization function only sets the clock date and time when it is unable to find a file named clockSet.txt on the SD card, ensuring that the initialization of the DS1307 only happens once in the InitializePeripherals function or until the file is deleted. <pre class="brush: csharp
public static void InitializeClock(DateTime dateTime) {
var clockSetIndicator = SdMountPoint + @"clockSet.txt";
try {
if (File.Exists(clockSetIndicator) == false) {
Clock.Set(dateTime);
Clock.Halt(false);
File.Create(clockSetIndicator);
}
} catch (Exception e) {
LogLine("InitializeClock: " + e.Message);
SignalCriticalError();
}
}
[/code] The LogTemperature function is the callback invoked by the Timer object every 10 seconds. The function indicates that it is working by turning the red LED on the shield ON and OFF. <pre class="brush: csharp
public static void LogTemperature(object obj) {
LedRed.Write(true);
}
[/code] The function reads the current time from the clock with Clock.Get() and takes a temperature sample with ThermoCouple.Read(). <pre class="brush: csharp
var tickStart = Utility.GetMachineTime().Ticks;
var now = Clock.Get();
ThermoCouple.Read();
var elapsedMs = (int)((Utility.GetMachineTime().Ticks - tickStart) / TimeSpan.TicksPerMillisecond);
[/code] Then, it concatenates a string containing the date, time and temperature expressed in Celsius and Fahrenheit, with each field separated by commas. <pre class="brush: csharp
var date = AddZeroPrefix(now.Year) + "/" + AddZeroPrefix(now.Month) + "/" + AddZeroPrefix(now.Day);
var time = AddZeroPrefix(now.Hour) + ":" + AddZeroPrefix(now.Minute) + ":" + AddZeroPrefix(now.Second) + ":" + AddZeroPrefix(elapsedMs);
var celsius = Shorten(ThermoCouple.Celsius.ToString());
var farenheit = Shorten(ThermoCouple.Farenheit.ToString());
var latestRecord = date + "," + time + "," + celsius + "," + farenheit;
[/code] To make the data more manageable, daily temperature files are created as needed, each one starting with the column headers expected for parsing the values in CSV format. <pre class="brush: csharp
var filename = SdMountPoint + BuildTemperatureLogFilename(now);
if (File.Exists(filename) == false) {
using (var tempLogFile = new StreamWriter(filename, true)) {
tempLogFile.WriteLine("date,time,celsius,fahrenheit");
}
}
[/code] The temperature sampling application lets the user remove the SD card from its socket so that the CSV files can be moved over to a PC for processing without losing data in the meantime. In order to do this, the application checks the state of the Card Detect pin before attempting file system I/Os. When the SD card is not present, the latest temperature record is preserved in the array list buffer until the SD card is put back in its socket. The array list data is then flushed to storage. <pre class="brush: csharp
if (CardDetect.Read() == false) {
using (var tempLogFile = new StreamWriter(filename, true)) {
if (Buffer.Count != 0) {
foreach (var bufferedLine in Buffer) {
tempLogFile.WriteLine(bufferedLine);
}
Buffer.Clear();
}
tempLogFile.WriteLine(latestRecord);
tempLogFile.Flush();
}
} else {
LogLine("No card in reader. Buffering record.");
Buffer.Add(latestRecord);
}
[/code] The temperature logging function expects to run out of memory if the array list buffer grows too large, in which case, all the records get purged. Other memory management strategies could be used to mitigate data loss in this case. However, this depends entirely on the requirements of the data logging application and is out of scope for this discussion. <pre class="brush: csharp
catch (OutOfMemoryException e) {
LogLine("Memory full. Clearing buffer.");
Buffer.Clear();
}
[/code] The temperature logging function also handles file system exceptions caused by the removal of the SD card and reacts by signaling the ResetPeripherals event. In turn, this lets the applications main loop know that the peripherals, and most specifically the SD card, need to be recycled and initialized again in order to recover from the error. <pre class="brush: csharp
catch (IOException e) {
LogLine("IO error. Resetting peripherals.");
Buffer.Add(latestRecord);
ResetPeripherals.Set();
}
[/code] <h5>Conclusion</h5> In this article, we took a shield designed for the Arduino and learned how to critically review the Arduino code libraries supporting it, drawing parallels with features offered by the .NET Micro Framework. This process allowed us to identify areas in the Arduino code which were not necessary to port over to C# such as SD card and file system handlers. It also allowed us to see the similarities in the way the Arduino and the Netduino handle I2C communications. Most importantly, we also learned the importance of reviewing a devices schematics and component datasheets to ensure that important features have not been omitted and potentially incorrectly implemented when considering using an unknown library: in the case of RTClib, we saw that the implementation was limited to the basic date and time functions of the DS1307, leaving out other useful features such as the clocks built-in RAM and the square wave generation functions. In our next article, well take on a much more complex shield and we will learn how to analyze Arduino libraries in depth before porting them from C/C++ to C#. <h6>Bio</h6> Fabien is the Chief Hacker and co-founder of Nwazet, a start-up company located in Redmond WA, specializing in Open Source software and embedded hardware design. Fabiens passion for technology started 30 years ago, creating video games for fun and for profit. He went on working on mainframes, industrial manufacturing systems, mobile and web applications. Before Nwazet, Fabien worked at MSFT for eight years in Windows Core Security, Windows Core Networking and Xbox. During downtime, Fabien enjoys shooting zombies and watching sci-fi. <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:21e6180e991b4430b459a0b00157f766
View the full article