Scott Hanselman

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

August 25, 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 twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
August 25, 2006 16:29
Hi Scott,
first let me note that I can't DL the CS file, and I'm not that good in C#.

Are you trurning the IR light on and off by powering the COM pin, or sending a "full" command that contains the "on" *and* the "off" bits as well?
my hunch is that manged code can't be trusted on this tiny time resolutions of miliseconds, thus one needs to prepare a "full" command (including a "light off" command).
Am I making any sense, or I'm just way off?
August 25, 2006 17:03
How about if you leave the 40kHz stuff to a hardware oscillator, then your code just supplies the 1.2ms and 0.6ms pulses? I'm sure you could find (or make) an oscillator with a 40kHz frequency and a 32% duty cycle. (Here's one that would work: http://www.electronic-circuits-diagrams.com/oscillatorsimages/oscillatorsckt1.shtml)
August 25, 2006 18:02
Wow, this brings back memories for me....

http://jasonf-blog.blogspot.com/2006/08/using-windows-to-drive-high-speed.html

I don't think you're going to get the consistency that you need from a user mode application, because it sounds very similar to issues that I experienced 5-7 years ago (I could be wrong, since operating systems and hardware have come a long way since then). In my case, I was able to slow down the hardware to a point where Windows was able to provide consistent results.

In your case, where the hardware cannot be modified, I personally would probably go with a man-in-the-middle solution (as one great podcaster keeps saying: all problems in computer science can be solved by an additional layer of abstraction).

Being familiar with microcontrollers, my thoughts immediately jump to a design that uses an AVR to communicate with your PC using full RS-232, and then drives the IR device using its digital I/O. In this scenario, you're no longer trying to drive the IR directly, but instead are just issuing commands to the AVR using some serial protocol. But, this also requires the assembly of additional circuitry and writing firmware for the AVR, so it's not a task for the lighthearted.
August 25, 2006 18:06
I haven't done any real-time programming in a Windows environment, but I think it might be a "challenge" to get consistent timing to pulse the IR through a C# or even Native C++ app...to really get a good measure on this here is a cheap computer based oscilloscope you could use to monitor your wave-form.
http://www.parallax.com/detail.asp?product_id=28119

If you are just looking for something to send IR signals to your devices, check this out
http://www.usbuirt.com/
They have an SDK but I haven't played with it.

Or if you do want to program a solution, try ordering a Basic Stamp
http://www.parallax.com/
where you have a little more control over the hardware
Good luck!
August 25, 2006 21:58
I like the idea of a dongle that is a hardware oscillator that sits between serial and the hardware. I think I'll try that.
August 26, 2006 5:54
You could consider rewiring DTR to the TX line and creating a bit stream to drive the pattern that you want. If you set the port to databits 8, parity None, start bits 0 so you have just the raw bits and a high speed it should give you the resolution that you require.
August 26, 2006 21:03
Bart - I thought about that....do you think I could truly get 600 microsecond timing with only 115200 bits per second?

I think that the *right* thing to do is hook up an oscillator...this is officially a hardware problem now. :)
August 27, 2006 15:25
Firstly I am totally speculating here, secondly big numbers (above about 1000 :P ) hurt my brain so I could be totally off, thirdly I am assuming remotes are not very fussy about exact timing because most universal remotes seem to work fine on most equipment.

You need to create a carrier of 40Khz so that would require a ON OFF or [10] at 40K times per second or 80Kbs.
You could try and configure your port for exactly 80000bps but 115200 is very close to 120Kbs that could use a 3bit [100] bit map (or a [110])

So a fully repeatable CARRIER ON bit map is 3 bytes long
[10010010] [01001001] [00100100]

And the CARRIER OFF is just 3 blank bytes.
[00000000] [00000000] [00000000]

If one bit at 115200 lasts 8.6us then 24 or 3 bytes lasts 208.3us.
So each approx 600us IR RC bit would be 9 bytes long ie. 3 CARRIER ON or 3 CARRIER OFF sequences.
All that you need to do is keep the RS232 port happily fed for the complete command sequence and the UART should take care of the rest. I think :)

Check out this guy. Has the same sort of Idea I think.
http://www.armory.com/~spcecdt/remote/IRremote.html

Comments are closed.

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