This 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.

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:

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?
INTERESTING 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.
Hosting By