Scott Hanselman

Performance of System.IO.Ports versus Unmanaged Serial Port Code

August 24, 2006 Comment on this post [8] Posted in Programming
Sponsored By

IrboardThis is obscure, but then, what do I end up blogging about that isn't obscure? I wish I knew why this stuff keeps happening to me.

I'm trying to do a little work with an external Infrared Transmitter from Iguanaworks connected to a 9-pin serial port.

I'm trying to pulse the IR to emulate a Sony Remote Control.

I'm using the new System.Diagnostic.Stopwatch class in .NET (a wrapper for the Win32 QueryPerformanceCounter API) that uses Ticks for its unit of measurement. On my system the Frequency was 2992540000 ticks per second.

The Sony remote I'm trying to emulate uses a 40kHz frequency, so it wants to flash the LED one cycle once every 1/40000 of a second. That means every 74814 ticks or every 25µs (microseconds are 1/1000000 of a second.)

I'm trying to send a header pulse of 2.4ms in length and I need to cycle the LED once every 25µs. I turn it on for 8µs and turn if off for 17µs. That means it will cycle 96 (2400µs) times for the header, 24 (1200µs) times for a space or zero, and 48 (600µs)times for a one. An image from San Bergmans illustrates:

The Iguanaworks IR uses DTR (Data Terminal Ready) to turn on the IR LED.

I've started with managed code, because I'm a managed kind of a guy. I started using System.IO.Ports like this:

public class ManagedIRSerialPort : IIRSerialPort

    {

        SerialPort port = null;

 

        public ManagedIRSerialPort(string portString)

        {

            port = new SerialPort(portString);

            port.RtsEnable = true; //needed for power!

            port.BaudRate = 115200;

            port.StopBits = StopBits.One;

            port.Parity = Parity.None;

            port.DataBits = 7;

            port.Handshake = Handshake.None;

        }

        public void Open()

        {

            port.Open();

        }

 

        public void On()

        {

            port.DtrEnable = true;

        }

 

        public void Off()

        {

            port.DtrEnable = false;

        }

 

        public void Close()

        {

            port.Close();

        }

    }

But I just couldn't get it to cycle fast enough. Remember, I need the header to take 2400µs total. In this screenshot, you can see it's taking an average of 30000µs! That sucks.

Managed

So I futzed with this for a while, and then Reflector'd around. I noticed the implementation of set_dtrEnable inside of System.IO.Ports.SerialStream was WAY more complicated than it needed to be for my purposes.

//Reflector'd Microsoft code
internal
bool DtrEnable

{

      get

      {

            int num1 = this.GetDcbFlag(4);

            return (num1 == 1);

      }

      set

      {

            int num1 = this.GetDcbFlag(4);

            this.SetDcbFlag(4, value ? 1 : 0);

            if (!UnsafeNativeMethods.SetCommState(this._handle, ref this.dcb))

            {

                  this.SetDcbFlag(4, num1);

                  InternalResources.WinIOError();

            }

            if (!UnsafeNativeMethods.EscapeCommFunction(this._handle, value ? 5 : 6))

            {

                  InternalResources.WinIOError();

            }

      }

}

All I figured I needed to do was call the Win32 API EscapeCommFunction to set the DTR pin high. One thing I learned quickly was that calling EscapeCommFunction was 4 times faster than calling SetCommState for the purposes of raising DTR.

public class UnmanagedIRSerialPort : IIRSerialPort

{

    IntPtr portHandle;

    DCB dcb = new DCB();

 

    string port = String.Empty;

 

    public UnmanagedIRSerialPort(string portString)

    {

        port = portString;

    }

 

    public void Open()

    {

        portHandle = CreateFile("COM1",

              EFileAccess.GenericRead | EFileAccess.GenericWrite,

              EFileShare.None,

              IntPtr.Zero,

              ECreationDisposition.OpenExisting,

              EFileAttributes.Overlapped, IntPtr.Zero);

 

        GetCommState(portHandle, ref dcb);

        dcb.RtsControl = RtsControl.Enable;

        dcb.DtrControl = DtrControl.Disable;

        dcb.BaudRate = 115200;

        SetCommState(portHandle, ref dcb);

    }

 

    public void On()

    {

        EscapeCommFunction(portHandle, SETDTR);

        //dcb.DtrControl = DtrControl.Enable;

        //SetCommState(portHandle, ref dcb);

    }

 

    public void Off()

    {

        EscapeCommFunction(portHandle, CLRDTR);

        //dcb.DtrControl = DtrControl.Disable;

        //SetCommState(portHandle, ref dcb);

    }

 

    public void Close()

    {

        CloseHandle(portHandle);

    }

 

    #region Interop Serial Port Stuff

   
    [DllImport("kernel32.dll")]

    static extern bool GetCommState(IntPtr hFile, ref DCB lpDCB);

 

    [DllImport("kernel32.dll")]

    static extern bool SetCommState(IntPtr hFile, [In] ref DCB lpDCB);

 

    [DllImport("kernel32.dll", SetLastError = true)]

    public static extern bool CloseHandle(IntPtr handle);

 

    [DllImport("kernel32.dll", SetLastError = true)]

    static extern bool EscapeCommFunction(IntPtr hFile, int dwFunc);

   
    //Snipped so you don't go blind...full file below!

    #endregion

}

Here's the NOT COMPLETE (Remember, it just does DTR) Unmanaged Serial Port class. Thanks PInvoke.NET for the structures to kick start this)!:
File Attachment: UnmanagedIRSerialPort.cs (10 KB)

As you can see I've got it abstracted away with a common interface so I can switch between managed serial and unmanaged serial quickly. I ran the same tests again, this time with MY serial port stuff:

Unmanaged

Sweet, almost 10x faster and darn near where I need it to be. However, it's not consistent enough. I need numbers like 2400, 600, 1200. I'm having to boost the process and thread priority just to get here...

 previousThreadPriority = System.Threading.Thread.CurrentThread.Priority;

 System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;

 System.Diagnostics.Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

...and undo it with...

 System.Threading.Thread.CurrentThread.Priority = previousThreadPriority;

 System.Diagnostics.Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal;

...and that's just naughty.

At this point, it's close, but I'm wondering if it's even possible to flash this thing fast enough. I'm at the limit of my understanding of serial ports (Is DTR affected by Baud Rate? Is 115200 the fastest? Would this be faster in C++ (probably not), or is there a faster way to PInvoke?)

Any ideas?

IrimageINTERESTING NOTE: Remember, you can't see IR (it's Infrared, not in our visible spectrum) but you can see it if you point it at a Webcam, which is what I've been doing to debug.

ANOTHER ASIDE: This is a uniquely high-power transmitter, that charges up a capacitor in order to provide a range of up to 10-meters. However, it requires a few minutes to charge up. I had no trouble getting output from it using Winlirc (the only officially supported software) but when I used my application, the transmitter would peter out and eventually go dim.

I fought with it for a while, then decided to RTFS (where "S" is "Schematic). The board layout is here. Notice that the RTS (Serial Port Ready-To-Send) Pin 7 goes straight to VIN. Duh! <slaps forehead>. They are sipping power off the Ready To Send pin and I'm not setting that pin hot.

port = new SerialPort(portString);
port.RtsEnable = true; //needed for power!
port.BaudRate = 115200;
port.StopBits = StopBits.One;
port.Parity = Parity.None;
port.DataBits = 7;
port.Handshake = Handshake.None;

So, if you ever find yourself using the High-Power LIRC Transmitter/Receiver in an unsupported way writing your own program, remember to set RTS high or you won't get any power.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Flip the endian-ness of a long in C#

August 24, 2006 Comment on this post [20] Posted in Programming
Sponsored By

Programming challenge:

Write me a function with this signature in C#:

public (unsafe?) long Reverse(long i, int bits)

...to flip the endian-ness (LSB/MSB) of a long, but just the # of significant bits specified.

Example, if the input is 376, with bits=11, the output is 244 (decimal, base 10).

376 = 00000101111000
244 = 00000011110100

Example, if the input is 900, with bits=11, the output is 270.

900 = 00001110000100
270 = 00000100001110

Example, if the input is 900, with bits=12, the output is 540.

900 = 00001110000100
540 = 00001000011100

Example, if the input is 154, with bits=4, the output is 5.

154 = 00000010011010
5   = 00000000000101

And make it FAST...;)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Citizen Mo

August 24, 2006 Comment on this post [11] Posted in Musings
Sponsored By

A big (and long and exhausting) day for the Hanselman family. My wife has just today become a citizen! She took her Civics test (Quick! What amendments to the Constitution specifically address voting rights?) We attended the ceremony this afternoon.

Her thoughts from when this process started (and the previous decade) are on her blog. She says she'll get another essay or two up soon. Congrats to the wife!

UPDATE: Mo has add a new entry to her blog entitled "American By Choice."

RedactedCIMG5734CIMG5715

As an interesting side note, Mo has a featured article in this month's issue of Multilingual Living Magazine (TOC PDF - subscripton required - $12 a year to subscribe) on how we met!

 

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

August 2006 - My spare time

August 24, 2006 Comment on this post [11] Posted in Musings
Sponsored By

For some reason, I just thought this screenshot was funny at 1:05AM. Something I'm working on in my spare time.

Irmadness1

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Death to Carriage Returns and Linefeeds

August 23, 2006 Comment on this post [8] Posted in Musings | Subversion
Sponsored By

Labels_typewriter130 years later and the typewriter is still slowing me down. When you type on a typewriter (for those of you under 25(?) who've never seen one) and reach the end of a line you grasp the "carriage return bar" which would return the carriage to the home position while simultaneously rotating the platen to feed the paper.

Aside: I spent the better part of the summer 1985 manually typing up the screenplay to Ghostbusters (as a reference material) on a manual Smith-Corona Typewriter.

Fast-forward to Teletype machines and now the carriage return/line feed combination is encoded into the wire protocol, then baked into ASCII text encoding itself in the 60s.

Fast-forward to lunchtime today when I was trying to commit some user-submitted changes to DasBlog via TortoiseSVN and got this fantastic error message "Commit failed: file has inconsistent newlines." What application would be so clever as to patch a file with consistent CR/LF pairs with a band-aid of just LFs? Why TortoiseMerge of course! But I'm not bitter.

Badnewlines1

How do you fix something like this? Well, a number of ways.

Here's screenshots of the same file opened in Notepad++ and Notepad2.

Bad newlines in Notepad++

Bad newlines in Notepad2

Notepad2 shows the the CR/LF status in the status bar and also optionally at the end of each line. It also lets you switch line endings by simply double clicking on the status bar indicator. Slick. Anyway, crisis averted and changes successfully committed. 

Will we ever be rid of this problem?

No, just like Intel will always been little-endian while the rest of the world will be big-endian. As an unrelated aside from Alex McLellan:

The terms big-endian and little-endian were taken from the Big-Endians and Little-Endians of Gulliver’s Travels, when Lilliput and Blefuscu Gulliver finds two factions fighting over which end of a boiled egg should be cracked open.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.