Export (0) Print
Expand All

Serial Communication with the .NET Compact Framework

.NET Compact Framework 1.0
 

Christian Forsberg
businessanyplace.net

March 2006

Applies to:
   Windows Mobile 2003–based Pocket PCs
   Microsoft Visual Studio .NET 2003
   Microsoft .NET Compact Framework version 1.0

Summary: Learn how to make your applications communicate over a serial port and how to handle common issues when implementing serial solutions. After a general discussion about serial communication and a common serial communication standard, this article demonstrates a practical use of the technologies through sample source code written in C#. (35 printed pages)


Download Serial_Comm_NETCF.msi from the Microsoft Download Center.

Contents

Introduction
Serial Communication in Industry
Serial Resources
Testing Serial Solutions
Graphics with GDI+
Pocket PC Sample
Smartphone Sample
Simulator Sample
Code Walkthrough
Using Serial Ports in the Emulator
Conclusion

Introduction

Many professional mobile solutions involve communication over a serial (also known as a communications [COM] or RS232) port. Even though most mobile devices do not have a physical serial port, the cradle usually provides one. Also, there are several emulations of serial ports; the most common examples are infrared, Bluetooth, universal serial bus (USB), and virtual serial ports.

You can, therefore, connect a mobile device to existing equipment that has a serial interface. Because many portable instruments and other field equipment have such an interface, the match with a mobile device like a Pocket PC or Smartphone is perfect. Modern devices and the proper software can make those instruments come to life in a new manner. Information can be presented in a highly graphical and user-friendly fashion, and data from different equipment can be combined to create better support for quick decisions.

After describing serial communication technology, a serial standard, and the tools—and providing some general advice—this article will show and examine a real-world sample.

Serial Communication in Industry

Most commonly, Global Positioning System (GPS) equipment uses serial ports to provide position data. But there are many other examples where serial communication is still widely used, and these include:

  • Modems (and file transfer protocols like Xmodem)
  • Cellular phones
  • Digital cameras
  • Bar-code scanners (and magnetic stripe readers)
  • Laboratory instruments
  • Field equipment
  • Marine equipment
  • Other equipment (such as sensors, scales, balances, gages, and meters)

Communication with modems includes using instructions for configuration and for making calls. The instructions used are normally the Hayes AT commands. Even if today modems are normally used to connect to a modem pool, the Internet, or a company network, there is also an option to call another peer (that has a modem) and chat or send files. This type of call can be achieved with a program like HyperTerminal, which comes with desktop computer versions of Windows. HyperTerminal includes traditional serial file transfer protocols like Xmodem, Ymodem, and Zmodem. On the device side, many of the commercial products like MegaPipe .NET Compact Framework Class Library include this capability.

The most common use of cellular phones for serial communication is probably as a modem for another device, such as a laptop computer or Pocket PC. The communication is the same as for modems (uses the Hayes AT commands mentioned earlier). Most cellular phones support this functionality both through a physical cable and through Bluetooth. With digital cameras (including those built into cellular phones), a serial link is mainly about file (image) transfer by means of a serial connection, but in some cases, the serial link can also be used to control (configure) the camera.

Bar-code scanners (and magnetic stripe readers, such as credit card readers) can also provide scanned bar codes as a serial communications feed. A product like the BaracodaPencil can provide your device with bar-code data without a cable (by using Bluetooth). It even works as a stylus.

Most laboratory instruments today still use serial communication to report measurements and other results. There are a number of well-defined standards and protocols, and the option to read serial data on mobile devices opens new, more mobile, opportunities for laboratory work. For example, you can instantly see new laboratory results on your Smartphone instead of going to the laboratory to pick up the results.

In the field, many instruments still deliver data only by using a serial interface. The scale is very wide and ranges from configuring forklifts and other industrial equipment to all sorts of handheld instruments, like laser and three-dimensional scanners that professionals use in land surveying and construction.

Marine Equipment

Marine equipment includes a full range of products like antennas, autopilots, compasses, GPS devices, sonars, radars, and instruments for depth, speed, and wind. The products are available from manufacturers like RayMarine, Simrad, and Furuno.

The area of connecting to marine equipment has come far with regards to integration. Most equipment can be connected with interface units like a Bluetooth-enabled National Marine Electronics Association (NMEA) multiplexer from ShipModul, and can deliver information about position, water depth, speed, and so on. If a traditional multiplexer is already in place, a Bluetooth dongle can translate the serial port signals to Bluetooth. Products include Serial Dongle from Baracoda, F2M01 plug from Free2move, BlueAirPlug from Inventel, Bluetooth RS232 Adapter from LinTech, PROMI SD from Merlin Systems, and Cordless Serial Adapter from Socket Communications.

Most GPS devices use the NMEA standard. The use of serial communication for capturing positioning data according to this standard is an area fairly well explored. The NMEA standard, however, covers much more than just the positioning data. It covers data reported by all of the equipment mentioned previously—and more.

NMEA—More than GPS

In the most widely used version of the NMEA standard (0183), about 10 percent of the information types, or sentences, are related to positioning. The more than 70 remaining sentences include other navigational information, like wind direction and speed, water depth and temperature, speed, distance, autopilot, and radar.

An example of a serial NMEA feed looks like the following code example.

$IIAPB,A,A,0.68,L,N,,,236.5,T,WP07,236.5,T,236.5,T*39
$IIBWC,140000.00,5410.000,N,01300.057,E,236.5,T,236.5,M,5.7,N,WP07*3D
$IIDBT,66.0,f,20.0,M,8.7,F*1C
$IIGGA,140010.00,0127.013,S,04831.001,W,1,07,1,0,M,,,,*2C
$IIGLL,0127.013,S,04831.001,W,140010.00,A*11
$IIGSA,,3,20,11,07,01,25,14,04,,,,,,,1,*79
$IIGSV,2,1,24,20,75,255,43,11,55,150,46,07,37,288,39,01,28,193,48*6F
$IIHDM,9.4,M*2F
$IIHDT,11.9,T*1B
$IIMTW,10.0,C*12
$IIMWV,357.7,R,20.3,N,A*0A
$IIMWV,1.3,T,14.0,N,A*0C
$IIRMA,A,0127.013,S,04831.001,W,,,4.61,7.7,1,E*49
$IIRMB,A,0.68,L,WP07,WP06,5410.000,N,01300.057,E,5.7,236.5,4.5,*79
$IIRMC,140010.00,A,0127.013,S,04831.001,W,4.61,7.7,110204,1,E,A*0A
$IIVHW,11.9,T,9.4,M,6.3,N,12.3,K*54
$IIVLW,1500.0,N,1500.0,N*4D
$IIVWR,14.2,R,20.3,N,10.2,M,39.6,K*40
$IIVWT,10.6,L,14.0,N,7.0,M,27.3,K*62
$IIXTE,A,A,0.68,L,N*77

As shown, each sentence begins with a dollar sign ($) character followed by a two-character talker identifier. A talker is a code for the equipment that provides the serial feed; in this case, II means integrated instrumentation. Other common talkers are GP for GPS, AP for autopilot, SD for sounder depth, and HC for heading (magnetic) compass. The next three characters are the sentence identifier and are followed by a number of data items (separated by commas) specific to the sentence. Each sentence also includes a final checksum (after the asterisk [*] character in each line). The sentence identifiers found in the preceding example feed are:

  • APB: autopilot sentence (B)
  • BWC: bearing and distance to waypoint
  • DBT: depth below transducer
  • GGA: GPS fix data
  • GLL: geographic position—latitude/longitude
  • GSA: GPS Dilution of Precision (DOP) and active satellites
  • GSV: satellites in view
  • HDM: heading magnetic
  • HDT: heading true
  • MTW: water temperature
  • MWV: wind speed and angle
  • RMA: recommended minimum navigation information (A)
  • RMB: recommended minimum navigation information (B)
  • RMC: recommended minimum navigation information (C)
  • VHW: water speed and heading
  • VLW: distance traveled through water
  • VWR: relative wind speed and angle (apparent)
  • VWT: true wind speed and angle
  • XTE: cross-track error, measured

Now, this article takes a closer look at some of the more common sentences, starting with the format of the DBT sentence provided by a sonar device, as shown in the following code example.

       1   2 3   4 5   6 7
       |   | |   | |   | |
$--DBT,x.x,f,x.x,M,x.x,F*hh<CR><LF>

1) Depth, feet
2) f = feet
3) Depth, meters
4) M = meters
5) Depth, Fathoms
6) F = Fathoms
7) Checksum

The first data items are the depth in feet (1) and the unit (2), followed by the depth in meters (3) and the unit (4). Next is the depth in fathoms (5) and the unit (6). The last data item is the checksum (7) of the sentence.

The format of the GLL sentence provided by a GPS device is as follows.

       1       2 3        4 5         6 7
       |       | |        | |         | |
$--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,A*hh<CR><LF>

1) Latitude
2) N or S (North or South)
3) Longitude
4) E or W (East or West)
5) Universal Time Coordinated (UTC)
6) Status A - Data Valid, V - Data Invalid
7) Checksum

The position (both latitude and longitude) is provided in the format of degrees, minutes, and seconds. For example, the latitude data item (1) may be 0127.013, which means 1 degree and 27.013 minutes (or in normal format, 1° 27.013').

The format of the MTW sentence provided by a thermometer is as follows.

       1   2 3
       |   | | 
$--MTW,x.x,C*hh<CR><LF>

1) Degrees
2) Unit of Measurement, Celcius
3) Checksum

Note that degrees are always provided in Celsius, and if degrees Fahrenheit are requested, that value needs to be calculated.

The format of the MWV sentence provided by a wind measuring device is as follows.

       1   2 3   4 5
       |   | |   | |
$--MWV,x.x,a,x.x,a*hh<CR><LF>

1) Wind Angle, 0 to 360 degrees
2) Reference, R = Relative, T = True
3) Wind Speed
4) Wind Speed Units, K/M/N
5) Status, A = Data Valid
6) Checksum

The reference data item (2) indicates whether the angle provided (1) is relative to the boat or the absolute compass direction (true). The true angle can be provided only if heading information is available. The wind speed (3) can be provided in three different units (kilometers per hour [km/hr], meters per second [m/hr], or knots [N]) (4).

The format of the VHW sentence looks like the following.

       1   2 3   4 5   6 7   8 9
       |   | |   | |   | |   | |
$--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh<CR><LF>

1) Degress True
2) T = True
3) Degrees Magnetic
4) M = Magnetic
5) Knots (speed of vessel relative to the water)
6) N = Knots
7) Kilometers (speed of vessel relative to the water)
8) K = Kilometers
9) Checksum

The difference between true (1) and magnetic (3) heading angle is that the true heading is the magnetic heading corrected for magnetic variation reported for the current position by the GPS device.

This information will become vital when you need to implement the sample application. But first, you will look at some of the serial communication resources available.

Serial Resources

Serial Port Central is a good source for general serial port information. If you are not familiar with the technology, this is the place to learn about each pin in a typical serial cable, and much more.

For the .NET Compact Framework developer, a number of resources are available for implementing serial communication. The following are some available resources as C# source code for the .NET Compact Framework:

All implementations have something to contribute, but if you are using the .NET Compact Framework 1.0, the OpenNETCF implementation is recommended. The .NET Compact Framework 2.0 includes support for serial communication (in the "System.IO.Ports" namespace).

A number of commercial components are also available for implementing serial communication in the .NET Compact Framework. Examples include (in alphabetical order):

The commercial products include more than mere serial communication support, and the recommendation is to evaluate the products (most provide trial versions that you can download) to see how they fit the requirements.

Testing Serial Solutions

In an ideal situation, you have all of the hardware set up locally to test in an environment that is identical to a real environment. However, in many cases, you need to set up a test environment that does not require all of the real-world equipment. You then need to simulate the actual communication traffic, and there are a few options available.

Most simulations require the communication to be recorded somehow. The usual solution is to use some software to store the communication from a real-world session. There are many tools available to do this, and examples are Serial Port Monitor from Eltima Software and Serial Monitor from HHD Software. A free example that actually runs on the device is CommLog for CE from 5dtool.

A number of simulation options are available:

  • Local simulation by using a recorded file.
  • Local simulation by using a data feed simulator.
  • Remote simulation by using a data feed simulator.

The first option is the most obvious and can take the testing very far—considering the actual functionality of the application. This option is also a very common way of providing a disconnected demonstration version of an application for serial communication. Usually, a background thread is used to read a previously recorded session and feed it to the receiving code, just as if the data came from the serial port. However, this simulation option will not test many issues related to the actual use of the serial port.

The second option means that the tested application connects to a separate simulator application over a serial port. Because most devices do not have more than one serial port (if any), you can use software to simulate serial ports—in other words, as virtual serial ports. These products can also create a virtual null modem cable connection between two of the virtual serial ports. Then, the tested application can connect by using one of the connected serial ports, and the simulation application can connect to the other.

The third option may be one device connecting to another device by using a null modem cable. The other device would then be running the simulator application. An advantage with this option is that it is even more true serial communication involving the actual communication hardware (for example, physical ports and cables). The tested application running on the device can also be connected to a computer running the simulation application. Actually, if you use software to create virtual ports on the computer, two virtual ports can be set up on the computer and be connected through a virtual null modem cable connection. If the emulator is also connected to one of these ports and the other to the simulation software running on the computer, you can use remote simulation without the need for anything more than a computer.

Using the last option (except for the virtual serial ports on the computer) means that the tested application communicates through the serial port as intended. This communication enables very accurate testing, but as always, some true real-world final testing connected to the actual equipment is a good idea.

Example products that provide the functionality to create virtual ports and also virtual null modem cable connections between these virtual ports include: GpsGate from Franson Technology, Virtual Serial Ports Driver CE and VSPD Mobile Phone Edition from Eltima Software,

and Virtual Serial Port Kit CE from FabulaTech. For the desktop computer, there are a number of products, like Virtual Serial Port products from Constellation Data Systems.

A side note is that with a product like this, serial communication can also be an option for interprocess communication (IPC) between applications on the device.

Next, this article looks at a sample that implements most of the topics this section discussed.

Graphics with GDI+

Because many solutions that include serial communication handle measured information that needs to be presented in a user-friendly way, the need to use more graphics becomes apparent. Although many third-party libraries and controls are available to handle the graphics for you, the resources in the .NET Compact Framework are quite powerful. Windows CE implements a subset of the desktop GDI+ API, and the .NET Compact Framework wraps the most important part of it.

Most of what you can do in the full .NET Framework is available in the .NET Compact Framework. If you need any of the functionality missing from the full .NET Framework, you should see the article XrossOne Mobile GDI+ for the Microsoft .NET Compact Framework and the open source project that it covers. It is a pure managed code (C#) implementation that implements all GDI+ functionality available in the full .NET Framework.

All of the graphics in this article's download code sample were created through the standard .NET Compact Framework GDI+ functionality (found in the "System.Drawing" namespace). The functionality was divided into two logical libraries: one for the drawing of the log charts (named LogChart) and the other for the drawing of the compass (named CompassChart). The implementation was purposely made very simple, but the structure of the code is suitable for converting the libraries into custom controls.

Most of the implementation is standard—draw lines and texts combined with the necessary portion of arithmetic. However, it is worth mentioning how the compass needle was created in the compass charts (see Figure 7 and Figure 8 later in this article). After the needle point is calculated, the base of the needle is determined by defining two points 135 degrees in each direction from needle point angle, as shown in Figure 1.

Figure 1. Compass needle drawn by means of GDI+

Another interesting implementation is how the boat shape was created in the wind compass chart (see Figure 7 later in this article). First, a new temporary drawing area (memory bitmap) was created, and then two ellipses and a line were drawn as shown in Figure 2.

Figure 2. Boat image created by means of GDI+

Then, the marked (light blue) area was copied from the temporary area into the actual chart bitmap.

With sufficient knowledge about what the tools can do, you can do most of what you need without using a third party. The code walkthrough later in this article will provide more details about the graphics implementation. But first, this article looks at the download code sample.

Pocket PC Sample

The Marine Anyplace sample was created with Microsoft Visual Studio .NET 2003, in C#, and targets the .NET Compact Framework. The sample uses the Smart Device Framework from OpenNETCF, which is also available as source code. The sample shows how to do serial communication, and how you can do serial communication by using various types of simulation.

The idea behind the download code sample is that a Pocket PC or Smartphone can receive the data that the marine equipment provides. With the use of Bluetooth technology (see the example product mentioned earlier), the data can be received without a physical connection (without using a cable). The download code sample has a number of logical screens. The first screen is position information, as shown in Figure 3.

Figure 3. Current position

The position appears in the format of degrees followed by minutes, seconds, and direction, and latitude appears above longitude. When you tap the Map button, the map for the current position is retrieved from the MapPoint Web Service. When you tap the Next button, the second screen with the current depth appears, as shown in Figure 4.

Figure 4. Depth

Note that the measured depth is saved in a log type of chart that constantly moves to the left. When you tap the Toggle button, you switch the depth units between feet, meters, and fathoms. When you tap Next, the screen with the water temperature indication appears, as shown in Figure 5.

Figure 5. Water temperature

The Water Temperature screen uses the same type of diagram. If you tap Toggle, the measurements switch between Celsius and Fahrenheit. Tapping the Next button takes you to the screen for speed indication, as shown in Figure 6.

Figure 6. Speed

Again, the Speed screen uses a log diagram. If you tap the Toggle button again, the units change from knots to kilometers per hour. Taping the Next button takes you to the screen for wind information, as shown in Figure 7.

Figure 7. Wind

If you tap the Toggle button on the Wind screen, the measurement options for wind direction change between relative and true. Tapping Next opens the final screen with information about the direction (heading), as shown in Figure 8.

Figure 8. Heading

On the Heading screen, if you tap Toggle, the measurement switches between magnetic and true (the magnetic heading is corrected for magnetic variation).

Smartphone Sample

The Smartphone version of the Marine Anyplace sample was also created with Microsoft Visual Studio .NET 2003, in C#, and targets the .NET Compact Framework. The sample uses the Smart Device Framework (from OpenNETCF, which is also available as source code. The sample shows how to do serial communication, and how you can do serial communication by using various types of simulation.

Note   The code that you can run on the Smartphone is almost identical to the code that was developed for the Pocket PC.

The first screen is the position information, as shown in Figure 9.

Figure 9. Current position

The Menu soft key includes the commands Map, Back, and Done. When you select the Map command, the map for the current position is retrieved from the MapPoint Web Service. When you press the Next soft key, the screen with the current depth appears, as shown in Figure 10.

Figure 10. Depth

When you select the Toggle command, you switch the depth units between feet, meters, and fathoms. When you press the Next soft key, the screen with the water temperature indication appears, as shown in Figure 11.

Figure 11. Water temperature

On the Water Temperature screen, you can select Toggle to switch the measurements between Celsius and Fahrenheit. Pressing the Next soft key takes you to the screen for speed indication, as shown in Figure 12.

Figure 12. Speed

Selecting the Toggle command again changes the units from knots to kilometers per hour. Pressing the Next soft key takes you to the screen for wind information, as shown in Figure 13.

Figure 13. Wind

If you select the Toggle command on the Wind screen, the measurement options for wind direction switch between relative and true. Pressing the Next soft key opens the final screen with information about the direction (heading), as shown in Figure 14.

Figure 14. Heading

Selecting Toggle on the Heading screen switches the measurement between magnetic and true (the magnetic heading is corrected for magnetic variation).

Simulator Sample

The simulator sample was also created with Microsoft Visual Studio .NET 2003, in C#, and targets the .NET Framework 1.1.

This sample shows how you can use a desktop computer application and a recorded serial communication session to simulate serial traffic for another application. The sample consists of only one screen, as shown in Figure 15.

Figure 15. Serial simulator on desktop computer

On the File menu, there is a command to start the simulation. On the Options menu, there is a command to show or hide the display of communicated data.

Code Walkthrough

The code for setting up the serial communication is as follows.

serialPort = new Port("COM1:", new HandshakeNone());
serialPort.Settings.BaudRate = BaudRates.CBR_9600;
serialPort.RThreshold = 1;
serialPort.InputLen = 1;
serialPort.DataReceived +=
    new OpenNETCF.IO.Serial.Port.CommEvent(serialPort_DataReceived);
serialPort.Open();

The download code sample uses the Smart Device Framework from OpenNETCF to handle the serial communication. In the previous code, the serial port instance (serialPort) is created and connected to the first serial port without any flow control, and it uses 9600 baud. The RThreshold property is set to raise events on every character received, and the InputLen property is set to indicate that only one character should be read when the Input method is called. The event handler serialPort_DataReceived that will be called when data is available is set up, and then the serial port is opened.

Before the application closes, the serial port needs to be closed by means of the following code.

if(serialPort.IsOpen)
    serialPort.Close();

When new information arrives, the DataReceived event is called and is implemented as follows.

private void serialPort_DataReceived()
{
    byte[] inputData = new byte[1];
    string s = String.Empty;
    while(serialPort.InBufferCount > 0)
    {
        inputData = serialPort.Input;
        if(inputData[0] != '\r')
            s += Encoding.ASCII.GetString(inputData, 0, inputData.Length);
        else
        {
            parseNmeaSentence(s);
            s = string.Empty;
        }
    }
}

After some declarations, a loop runs until the serial port's buffer is empty—that is, when the InBufferCount property indicates there is more data to receive being greater than zero. Each character is read, translated to ASCII, and appended to the temporary string ("s"). When a carriage return character is received, the line is passed to the helper class for parsing NMEA sentences.

Parsing NMEA Sentences

Because the DataReceived event that is raised whenever new data has arrived is called from a background thread that the serial port implementation creates, you cannot update the user interface controls directly from this thread. When parsing NMEA sentences, this action raises events that update the user interface controls. To prevent the handling of this issue in each event, the issue is handled before these events are raised. The updating of the user interface controls needs to occur on the same thread where they were created, by means of the following code.

private string nmeaSentence;
private void parseNmeaSentence(string s)
{
    nmeaSentence = s; 
    this.Invoke(new EventHandler(parseNmeaSentenceEventHandler));
}
private void parseNmeaSentenceEventHandler(object sender,
    System.EventArgs e)
{
    nmea.ParseSentence(nmeaSentence);
    nmeaSentence = string.Empty;
}

You can use the private class variable, nmeaSentence, to pass data between the threads. If you need a cleaner implementation, you can use something like the ControlInvoker class. On the user interface thread, nmea is called, which is the instance of the helper class for parsing NMEA sentences.

The implementation of the parsing method, ParseSentence in the NmeaHelper class looks like the following code example.

public void ParseSentence(string nmeaSentence)
{
    // NMEA sentence must include talker and identifier,
    // first character must be '$', and a maximum of 82 characters is
    // allowed
    if(nmeaSentence.Length < 6) return;
    if(nmeaSentence[0] != '$') return;
    if(nmeaSentence.Length > 82) return;

    // Remove leading '$'
    string sentence = nmeaSentence.Substring(1);

    // Checksum control (if '*' found)
    int starpos = sentence.IndexOf('*');
    if(starpos >= 0)
        if(!checksum(sentence.Substring(0, starpos),
                    sentence.Substring(starpos + 1)))
            return;

    // Get identifier
    string identifier = sentence.Substring(2, 3);

    // Get fields
    string[] fields = sentence.Substring(6).Split(',');

    switch (identifier)
    {
        case "DBT": // Depth below transducer 
            dbt(fields);
            break;

        case "GLL": // Depth below transducer 
            gll(fields);
            break;

        case "MTW": // Water temperature 
            mtw(fields);
            break;

        case "MWV": // Wind speed and angle 
            mwv(fields);
            break;

        case "VHW": // Water speed and heading
            vhw(fields);
            break;

        default:
            break;
    }
}

After some initial basic validation and the removal of the first dollar sign ($) character, the checksum of the sentence is validated. The sentence identifier (identifier) is extracted, along with the various data items. Depending on the sentence identifier, the respective private parsing method (named according to the sentence identifier) is called with the fields data item array.

The implementation of the NMEA checksum validation looks like the following code example.

private bool checksum(string s, string checksum)
{
    int sum = 0;
    for(int i = 0; i < s.Length; i++)
        sum = sum ^ (int)(s[i]);
    return (checksum == string.Format("{0:X2}", sum));
}

An example of a parsing method for a sentence identifier looks like the following.

/// <summary>
///  MWV - Wind Speed and Angle
///
///        1   2 3   4 5
///        |   | |   | |
/// $--MWV,x.x,a,x.x,a*hh<CR><LF>
///
/// 1) Wind Angle, 0 to 360 degrees
/// 2) Reference, R = Relative, T = True
/// 3) Wind Speed
/// 4) Wind Speed Units, K/M/N (K = kmph [km/h], M = mps [m/s], N = kt)
/// 5) Status, A = Data Valid
/// 6) Checksum
/// </summary>
/// <param name="field"></param>
private void mwv(string[] fields)
{
    // Only parse if data OK
    if(fields[4][0] != 'A')
        return;

    // Get field values
    decimal windAngle = Convert.ToDecimal(fields[0]);
    bool referenceRelative = fields[1][0] == 'R';
    decimal windSpeed = Convert.ToDecimal(fields[2]);
    string speedUnits = fields[3];

    // Raise event
    OnMwv(new MwvEventArgs(windAngle, referenceRelative, windSpeed, speedUnits));
}

This is the sentence identifier (MWV) for wind information (speed and direction, as an angle), and because the last data item is a status that indicates whether the data is valid, this sentence identifier is validated first. Then, the respective data items are retrieved from the passed array (fields), and the event for this sentence identifier is raised.

The event handler is declared and coded as follows.

public delegate void MwvEventHandler(object sender, MwvEventArgs e);
public event MwvEventHandler Mwv;
protected virtual void OnMwv(MwvEventArgs e) 
{
    if (Mwv != null) Mwv(this, e);
}

The implementation of the event arguments looks like the following code example.

public class MwvEventArgs : EventArgs
{
    public MwvEventArgs(decimal windAngle, bool referenceRelative,
                        decimal windSpeed, string speedUnits)
    {
        this.windAngle = windAngle;
        this.referenceRelative = referenceRelative;
        this.windSpeed = windSpeed;
        this.speedUnits = speedUnits;
    } 
    private decimal windAngle;
    public decimal WindAngle
    {
        get { return windAngle; }
    }
    private bool referenceRelative;
    public bool ReferenceRelative
    {
        get { return referenceRelative; }
    }
    private decimal windSpeed;
    public decimal WindSpeed
    {
        get { return windSpeed; }
    }
    private string speedUnits;
    public string SpeedUnits
    {
        get { return speedUnits; }
    }
}

The same logic applies to the other sentence identifiers (for more details, see this article's download code sample).

Previously in the user interface form, the NMEA helper and event handlers are set up as follows.

nmea = new NmeaHelper();
nmea.Dbt += new NmeaHelper.DbtEventHandler(nmea_Dbt);
nmea.Gll += new NmeaHelper.GllEventHandler(nmea_Gll);
nmea.Mtw +=new NmeaHelper.MtwEventHandler(nmea_Mtw);
nmea.Mwv +=new Marine.NmeaHelper.MwvEventHandler(nmea_Mwv);
nmea.Vhw +=new NmeaHelper.VhwEventHandler(nmea_Vhw);

The NMEA helper instance (nmea) is declared as a private class variable in the form. To continue with the sentence identifier that this article examined previously (MWV), the event handler for nmea is implemented as follows.

private void nmea_Mwv(object sender, MwvEventArgs e)
{
    if(relative != e.ReferenceRelative)
        return;

    if(windChart == null)
    {
        windChart = new CompassChart(windPictureBox.Width,
            windPictureBox.Height);
        windChart.WindCompass = true;
    }
    string s = string.Format("{0:0.0}", e.WindSpeed);
    if(e.SpeedUnits == "N")
        s += " kt";
    else if(e.SpeedUnits == "K")
        s += " kmph";
    else if(e.SpeedUnits == "M")
        s += " mps";
    windPictureBox.Image = windChart.Paint(e.WindAngle, s,
        relative ? "Relative" : "True");
}

The relative flag indicates whether the user wants to see the relative (to the boat) or true (compass) wind direction. If the event data provided matches the current state of that flag, the wind chart is created—if it doesn't already exist. The chart size is set from the size of the picture control windPictureBox, and the chart is indicated to be a wind chart (by setting the WindCompass property to true). The wind speed is formatted and the wind speed unit is set. Finally, the chart is displayed with parameters for wind angle, the wind speed with unit, and an indication of what wind data appears (relative or true).

Graphics with GDI+

The constructor of the CompassChart class looks like the following.

public CompassChart(int width, int height)
{
    this.width = width;
    this.height = height;
    forePen = new Pen(foreColor);
    foreBrush = new SolidBrush(foreColor);
    bitmap = new Bitmap(width, height);
    graphics = Graphics.FromImage(bitmap);
}

The size is saved in private variables (also available as properties), and some frequent painting objects (pen and brush in foreground color) are predefined. The memory bitmap instance to use for painting is created, and from it, the graphics context instance is derived.

Next, this article looks at how the method to paint the chart—the Paint method—is implemented. The first part, which draws the background, is shown in the following code example.

public Bitmap Paint(decimal angle, string topLabel, string bottomLabel)
{
    graphics.Clear(backColor);
    graphics.DrawEllipse(forePen, 5, 5, width - 10, height - 10);
    double a;
    int tickSize;
    string s;
    for(int i = 0; i < 360; i += 10)
    {
        a = 90 - (double)i;
        if(a < 0)
            a += 360;
        a = a / 180 * Math.PI;
        if(i / 30 == (double)i / 30)
        {
            tickSize = 15;
            s = i.ToString();
            if(windCompass && i > 180)
                s = ((int)(360 - i)).ToString();
            if(i > 0 && i < 100)
                s = s.PadLeft(4);
            SizeF size = graphics.MeasureString(s, font);
            graphics.DrawString(s, font, foreBrush,
                (int)((Math.Cos(a) * ((double)width/2 - 26) +
                (double)width/2 - size.Width/2)),
                height - (int)((Math.Sin(a) * ((double)height/2 - 26) +
                (double)height/2 + size.Height/2)));
        }
        else
            tickSize = 12;
        graphics.DrawLine(forePen,
             (int)((Math.Cos(a) * ((double)width/2 - tickSize) +
             (double)width/2)), height - (int)((Math.Sin(a) *
             ((double)height/2 - tickSize) + (double)height/2)),
             (int)((Math.Cos(a) * ((double)width/2 - 5) +  (double)width/2)),
            height - (int)((Math.Sin(a) * ((double)height/2 - 5) +
             (double)height/2)));
    }

After the chart area is Cleared (filled) with the background color, the chart circle is drawn with a five-pixel margin using the DrawEllipse method, and a loop is run for each tick mark (every 10°degrees) of the circle. Note that because GDI+ defines the zero-degree angle as the far right of a circle, the angle is rotated to make the top of the circle the zero-degree angle. Because the functions for calculating cosine and sine of an angle use radians, the angle in degrees is converted to radians (by multiplying with &pi;/180). Every 30 degrees, the tick mark is drawn a bit larger (7 pixels excluding the margin) than normal (5 pixels excluding the margin); a degree label is also drawn that is aligned with the tick mark using the DrawString method.

The boat figure is drawn as follows.

if(windCompass)
{
    Bitmap b = new Bitmap(width, height);
    Graphics g = Graphics.FromImage(b);
    g.Clear(backColor);
    g.DrawEllipse(forePen, 0, 0, (int)(width / 2), (int)(height * 0.75));
    g.DrawEllipse(forePen, (int)(width / 4), 0,
        (int)(width / 2), (int)(height * 0.75));
    g.DrawLine(forePen, (int)(width * 0.3), (int)(height * 0.6),
        (int)(width * 0.45), (int)(height * 0.6));
    graphics.DrawImage(b, new Rectangle((int)(width / 2 - width / 8),
        (int)(height / 2) - (int)(height * 0.6 / 2),
        (int)(width / 4), (int)(height * 0.6)),
        (int)(width / 4), (int)(height / 20) + 1, (int)(width / 4) + 1,
        (int)(height * 0.55),
        GraphicsUnit.Pixel, new ImageAttributes());
    g.Dispose();
    b.Dispose();
}

A new temporary memory bitmap b is created with a graphics context g. Its background is set to the background color, and two overlapping circles and a line are drawn on it (as shown earlier in Figure 2) using the DrawEllipse and DrawLine methods. Then, the area of the boat (see the light blue area in Figure 2) is copied into the chart bitmap (by means of the DrawImage method).

The following code example shows how the needle is drawn.

double rotatedAngle = 90 - (double)angle;
if(rotatedAngle < 0)
    rotatedAngle += 360;
double secondAngle = rotatedAngle - 135;
if(secondAngle < 0)
    secondAngle += 360;
double thirdAngle = rotatedAngle + 135;
if(thirdAngle < 0)
    thirdAngle += 360;
Point edge = new Point(((int)(Math.Cos(deg2Rad(rotatedAngle)) *
     ((double)width/2 - 15) + width/2)),
    height - ((int)(Math.Sin(deg2Rad(rotatedAngle)) *
     ((double)height/2 - 15) + height/2)));
Point backleft = new Point(
     ((int)(Math.Cos(deg2Rad(secondAngle)) *  10 + width/2)),
    height - ((int)(Math.Sin(deg2Rad(secondAngle)) * 10 + height/2)));
Point backright = new Point(
     ((int)(Math.Cos(deg2Rad(thirdAngle)) * 10 + width/2)),
    height - ((int)(Math.Sin(deg2Rad(thirdAngle)) * 10 + height/2)));
graphics.FillPolygon(new SolidBrush(backColor),
    new System.Drawing.Point[] { edge, backleft, backright});
graphics.DrawPolygon(new Pen(needleColor),
    new System.Drawing.Point[] { edge, backleft, backright});

As before, the angle is rotated to make the top of the circle the zero-degree angle. The resulting angle rotatedAngle is then used to define where to draw the point of the needle, which is calculated and stored in the edge variable. The base of the needle is defined by two points that are located 135 degrees in each direction with a radius of 10 pixels (as shown earlier in Figure 1). Before the needle is drawn with the DrawPolygon method, the needle area is cleared with the background color using the FillPolygon method.

Finally, the labels are drawn as shown by the following code example.

    s = topLabel;
    Font textFont = new Font("Tahoma", 14.25F, FontStyle.Bold);
    SizeF sf = graphics.MeasureString(s, textFont);
    graphics.DrawString(s, textFont, foreBrush,
        (int)((double)width/2 - sf.Width/2),
        height - (int)((double)height/2 + sf.Height/2 + height/6));

    s = bottomLabel;
    textFont = new Font("Tahoma", 9F, FontStyle.Bold);
    sf = graphics.MeasureString(s, textFont);
    graphics.DrawString(s, textFont, foreBrush,
        (int)((double)width/2 - sf.Width/2),
        height - (int)((double)height/2 + sf.Height/2 - height/6));

    return bitmap;
}

The DrawString method draws and centers the labels by determining the size of the text using the MeasureString method.

The implementation of the class that draws the log charts (LogChart) is similar to the compass chart. For more details, see this article's download code sample.

Local Simulation

The download code sample includes an option to do local simulation (often referred to as demo mode). Instead of initializing the serial port, the download code sample uses the following code.

Thread simulationThread = new Thread(new ThreadStart(simulation));
simulationThreadActive = true;
simulationThread.Start();

The preceding code example starts the simulation thread, which looks like the following.

private void simulation()
{
    StreamReader logFile = new StreamReader(logFileName);
    string s = logFile.ReadLine();
    while(simulationThreadActive)
    {
        parseNmeaSentence(s);
        Thread.Sleep(160);
        s = logFile.ReadLine();
        if(s == null)
        {
            logFile.Close();
            logFile = new StreamReader(logFileName);
            s = logFile.ReadLine();
        }
    }
    logFile.Close();
}

The log file name in the logFileName variable is the name of the previously recorded serial communication session. After the log file has been opened and the first line is read, the loop is run until the simulation stops. The loop sends the line to the NMEA helper class with a call to the parseNmeaSentence method (simulating that it comes from the serial port), and then waits a little less than a sixth of a second until another line is read from the file. When there are no more lines to read from the file, the loop is closed and reopened. Then, the reading of the log file starts from the beginning of the file again, thus simulating a continuous communication flow.

Before the application closes, the following line of code is needed to stop the background simulation thread.

simulationThreadActive = false;

When this simulation is active, the rest of the application logic is identical. A similar approach can be used from another application that simulates serial communication. Following is such an example written for the desktop computer and the full .NET Framework.

Desktop Simulator

The desktop computer simulator is a standard Windows Forms application that simply reads a previously recorded communication session and sends it to a serial port, thereby simulating a serial communication feed.

For the serial communication, a slightly modified version of the SerialPort sample on GotDotNet was used. The modifications were made only to make the interface more similar to the SerialPort class in the .NET Framework 2.0.

When the application starts, the serial port is opened as follows.

serialPort = new SerialPort("COM5");
serialPort.Open();

Note that only the serial port device name needs to be provided because you want to use the following default values:

  • Baud rate: 9600
  • Data bits: 8
  • Parity None
  • Stop bits: 1
  • Flow control: None

Because you are not receiving any data, you do not need to set up an event. However, if you want to set up an event, you must do so before the serial port is opened.

The code to define the event is as follows.

serialPort.DataReceived +=
    new SerialEventHandler(serialPort_DataReceived);

The event handler is as follows.

private void serialPort_DataReceived(object source, SerialEventArgs e)
{
    logTextBox.Text += serialPort.ReadExisting();
}

The only slight difference between the GotDotNet SerialPort sample and the implementation in the .NET Framework 2.0 is the naming of the event handler DataReceived and the event arguments SerialEventArgs. In .NET Compact Framework 2.0, they are named SerialDataReceivedEventHandler and SerialDataReceivedEventArgs respectively. But changing the code to be identical to the .NET Compact Framework 2.0 interface shouldn't be too difficult.

The following code example shows what happens when the user selects the Start menu command.

private void startMenuItem_Click(object sender, System.EventArgs e)
{
    if(!simulationThreadActive)
    {
        Thread simulationThread = new Thread(new ThreadStart(simulation));
        simulationThreadActive = true;
        simulationThread.Start();
        startMenuItem.Text = "Stop";
    }
    else
    {
        startMenuItem.Text = "Start";
        simulationThreadActive = false;
        return;
    }
}

If the simulation is not started, indicated by the flag simulationThreadActive, a new simulation thread is created and started. Also, the menu command name is changed to Stop. If the simulation is already started, the menu command name is changed back to Start and the flag is reset.

The code for the actual simulation looks like the following code example.

private void simulation()
{
    StreamReader logFile = new StreamReader(logFileName);
    string s = logFile.ReadLine();
    while(simulationThreadActive)
    {
        showCommunication(s);
        serialPort.WriteLine(s);
        Thread.Sleep(160);
        s = logFile.ReadLine();
        if(s == null)
        {
            logFile.Close();
            clearCommunication();
            logFile = new StreamReader(logFileName);
            s = logFile.ReadLine();
        }
    }
    logFile.Close();
}

The log file name, logFileName, is the name of the previously recorded serial communication session. After the log file has been opened and the first line is read, the loop is run until the simulation stops (by means of the Stop menu command on the File menu). The loop sends the line to the serial port by using the WriteLine method, and then waits a little less than a sixth of a second until another line is read from the file. When there are no more lines to read from the file, the loop is closed and reopened. Then, the reading of the log file starts from the beginning of the file again, thus simulating a continuous communication flow until it is manually stopped.

The code example used to show the simulated serial communication flow in the form is as follows.

private string comms;
private void showCommunication(string s)
{
    if(!showCommsMenuItem.Checked)
        return;
    comms = s;
    this.Invoke(new EventHandler(showComms));
}
private void showComms(object sender, System.EventArgs e)
{
    logTextBox.Text = comms + "\r\n" + logTextBox.Text;
    comms = string.Empty;
}
private void clearCommunication()
{
    if(!showCommsMenuItem.Checked)
        return;
    this.Invoke(new EventHandler(clearComms));
}
private void clearComms(object sender, System.EventArgs e)
{
    logTextBox.Text = string.Empty;
}

The menu command showCommsMenuItem indicates whether the form shows the communication. Because the simulation runs in a background thread, user interface controls should not be accessed from this thread. Therefore, the showCommunication method called from the simulation loop (see the previous code segment above) to display the flow needs to call the user interface thread. A private comms variable is used to pass the information between the threads, and the showComms event handler is used on the user interface thread to interact with the logTextBox form control. Note that the flow adds the new lines at the top, which makes the flow run down the form. The same logic is used to clear the displayed communication flow.

Using Serial Ports in the Emulator

To set up the Pocket PC and Smartphone emulators to use the serial ports on computers, use the following procedure:

To set up an emulator to use a desktop computer's serial ports

  1. Open Visual Studio .NET 2003, and then open a smart device project.
  2. On the Tools menu, click Options. The Options dialog box appears, as shown in the following figure.

    Click here for larger image

    Figure 16. Options dialog box. Click the thumbnail for a larger image.

  3. Select a device, and then click Configure to open the Configure Emulator Settings dialog box, as shown in the following figure.

    Figure 17. Configure Emulator Setting dialog box

  4. Select a serial port that you want to be the virtual serial port COM1 on the emulator.

    The COM port you select in the Serial port 1 box will be accessed as COM1 on the emulator, and the COM port you select as Serial port 2 will be accessed as COM2 on the emulator. In the example shown in the previous procedure, COM1 on the emulator will be mapped to COM3 on the desktop computer. If you are using software that creates virtual serial ports, you need to start this software before you configure and start the emulator.

Conclusion

Serial communication has been around for a while, and it will surely be here for a long time still. Equipped with a good wrapper for the serial communication, you can put more focus on other issues, like how to test by simulating serial flow, how to parse standard serial protocols, and how to present serial data in a user-friendly way. With the solutions presented in this article, and the reusable download code sample, you should have a good start in implementing your serial solution.

Show:
© 2014 Microsoft