网上有很多介绍红外遥控接收器制作的文章,但其中大部分是用单片进行红外解码,然后再通过串口或USB把解码后的按键信息传入到PC的。这样的电路制作起来,不仅造价相对偏高,而且需要对单片编程,这会令大部分软件开发爱好者望而却步。
最近看到一篇仅需要7个简单元器件的红外接收器,只需拿起烙铁,不需硬件编程就可以制作完成,原理图如下:
由原理图我们可知,红外接收头把接收的红外信号转换为高低电平通过串口的DSR管脚传入到PC,PC软件通过对DSR高低电平信号的时间曲线进行分析,从而获得相对应的按键信息。
红外遥控器一般采用脉宽调制的串行码,经38kHz的载频把红外信号发射出去。其编码信息一般由三部分组成:引导码、地址码和数据码。一般信号长度大约100ms左右,持续按键则重复发送(中间会有10ms以上的间歇)。
常态下,红外接收头的输出(OUT)都是高电平,引导码信号首先会令红外接收头输出一个大约10ms左右的低电平(不同遥控器有不同的时延),这可令接收设备从容判定信号的到来,而后面的地址码和数据码其电平高低变化就相对较快了,大概在几十或几百个微秒之间。
PC红外遥控软件一般选用Girder,在使用之前需要安装“SFH-56 plugin for Girder”这个插件(文件名"igor SFH-56P lug.dll"),否则不能正常处理我们这种电路的红外接收器信号。可悲的是我至今没找到这个插件,网上提供的很多链接都是坏的。
即使找到了这个插件,要想在我们自己编写的程序中使用也是困难的,因为Girder并没有为我们开发者提供API接口。
既然Girder能用软件实现红外解码,我们为什么不能呢?凡事都要开动大脑,积极行动才对,下面就是我自己焊接的一个红外接收器(元器件是在中发买的,一共不到10元钱,还富裕好多电阻、电容!)
(图下方的红外遥控器的接收器是基于USB的,仅支持Vista以上版本,并且不支持个人开发,不过今天它终于发挥了它应有的作用。当然用电视或VCD遥控器也是可以的)
硬件有了,但程序该从何编起呢?
1、由于接收到的红外信号在微秒级别中变化,对系统实时性要求较高,所以具备垃圾回收功能,实时性没有保证的C#,似乎完不成这种信号的接收功能,所以我们选择的是VC,由它实现高优先级的线程去进行信号接收。
2、由于红外遥控信号是脉宽调制的串行码,所以我们需要采集信号的宽度,显然采用一般的时钟函数来获取时间间隔是不可行的,因为精度太低,所以我们需用采用多媒体时钟和高精度计时的API函数。
3、一般我们按键持续时间为几秒钟,并且由于按键发出前有一个10ms左右的引导信号,所以我们的程序很容易判断出信号起始点,这样我们一次仅需要接收一定量的原始数据就可以完成初步信号采集工作。
4、对于我们的红外接收程序来说并不需要实际解码出红外信号到底包含了那些具体的信息,只要其能够区分出红外遥控上的各个按键就行。
5、由于红接收器是通过串口RTS管脚供电,且通过DSR传递红外信号的,所以我们的程序即使不接收数据,也要打开串口,不过仅需要处理RTS和DSR管脚的信号即可。
好了,动手去做,下面是用VC实现的一个DLL,其功能就是接收并记录红外信号的持续时间。核心代码如下:
DWORD WINAPI ThreadProc(LPVOID pParam)
{
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double
dfFreq;
int
iTime
=
0
;
//
微秒
//
获得计数器的时钟频率
QueryPerformanceFrequency(
&
litmp);
dfFreq
=
(
double
)
1000000.0
/
litmp.QuadPart;
DWORD ModemState,oldModemState
=
MS_DSR_ON;
//
EV_BREAK or EV_CTS or EV_DSR or EV_ERR or EV_RING or EV_RLSD or EV_RXCHAR or EV_RXFLAG or EV_TXEMPTY
//
SetCommMask(HSC_COM_Handle,EV_DSR);
//
DWORD EvtMask,dwError;
//
COMSTAT cs;
while
(HSC_Thread_RunFlag)
{
//
等待DSR信号发生变化
//
WaitCommEvent(HSC_COM_Handle,&EvtMask,&HSC_Ovread);
//
ClearCommError(HSC_COM_Handle,&dwError,&cs);
//
获得DSR的状态
GetCommModemStatus(HSC_COM_Handle,
&
ModemState);
ModemState
=
(ModemState
&
MS_DSR_ON);
if
(ModemState
==
oldModemState)
continue
;
oldModemState
=
ModemState;
//
清计数
InterlockedExchange(
&
HSC_NUM,
0
);
//
开始接收数据
if
(HSC_State
==
0
&&
ModemState
==
0
)
{
QueryPerformanceCounter(
&
litmp);
QPart1
=
litmp.QuadPart;
HSC_State
=
1
;
//
复位计数
InterlockedExchange(
&
HSC_NUM,
0
);
InterlockedExchange(
&
HSC_Index,
0
);
//
开启定时器
HSC_TimerID
=
timeSetEvent(
10
,HSC_Accuracy,MMTimer,NULL,TIME_PERIODIC);
continue
;
}
//
接收数据状态
if
(HSC_State
==
1
)
{
QueryPerformanceCounter(
&
litmp);
QPart2
=
litmp.QuadPart;
//
--
if
(ModemState
==
0
)
{
iTime
=
(
int
)((QPart2
-
QPart1)
*
dfFreq);
}
else
{
iTime
=
(
int
)((QPart1
-
QPart2)
*
dfFreq);
}
if
(HSC_Index
<
HSC_BufferSize)
*
(HSC_Buffer
+
HSC_Index)
=
iTime;
InterlockedIncrement(
&
HSC_Index);
//
--
QPart1
=
QPart2;
}
}
return
STILL_ACTIVE;
}
如果采用WaitCommEvent函数,你会发现CPU使用时间会很低,不过它会让接收程序无法正常退出,所以只好注释掉该函数了,此时你会发现CPU使用时间会很高。
原始数据一旦采集完毕,剩下的就由C#程序大显身手吧。
C#中DLL的接口函数如下:
const string DllPath = @"YFHSCollect.dll";
[DllImport(DllPath)]
public static extern Int32 HSCStart(Int32 COM, Int32 delay, Int32 BufferSize);
[DllImport(DllPath)]
public static extern Int32 HSCEnd();
[DllImport(DllPath)]
public static extern Int32 HSCData(int[] intData);
我封装了一个类,一旦有按键信息,就会触发一个Click事件。此外程序还具备自学习功能,并且可以把学习后的结果序列化到一个XML文件中去,这样下次再按键就可以识别出键名了。
主程序中测试代码如下:
public
partial
class
frmMain : Form
{
YFHWCollect hw
=
null
;
int
[] hwData
=
null
;
public
frmMain()
{
InitializeComponent();
hw
=
new
YFHWCollect(
this
,
1
);
hw.Click
+=
new
YFHWCollect.HWEventHandler(hw_Click);
}
void
hw_Click(
object
sender, HWEventArgs e)
{
string
strInfo
=
""
;
for
(
int
i
=
0
; i
<
e.lstData.Count; i
++
)
{
for
(
int
j
=
0
; j
<
e.lstData[i].Length; j
++
)
{
strInfo
+=
e.lstData[i][j].ToString()
+
"
"
;
}
strInfo
+=
"
\r\n
"
;
}
txtInfo.Text
=
strInfo;
lblKeyName.Text
=
e.KeyName
+
"
(
"
+
(e.Interval
/
10
).ToString()
+
"
ms)
"
;
hwData
=
e.Data;
picBar.Refresh();
}
private
void
btnCommand_Click(
object
sender, EventArgs e)
{
if
(btnCommand.Text
==
"
开始
"
)
{
btnCommand.Text
=
"
停止
"
;
hw.Start();
}
else
{
btnCommand.Text
=
"
开始
"
;
hw.End();
}
}
private
void
btnStudy_Click(
object
sender, EventArgs e)
{
hw.Study(txtKeyName.Text);
}
private
void
picBar_Paint(
object
sender, PaintEventArgs e)
{
int
width
=
picBar.Width, height
=
picBar.Height;
e.Graphics.DrawLine(
new
Pen(Color.Gray),
0
, height
/
2
, width, height
/
2
);
if
(hwData
!=
null
)
{
float
Len
=
0
;
foreach
(
int
l
in
hwData)
{
Len
+=
l;
}
float
dx
=
width
/
Len,DX
=
0
;
Pen p
=
new
Pen(Color.Green);
float
Y
=
0
, Y1
=
height
/
4
,Y2
=
(
float
)(height
*
3.0
/
4.0
);
float
X
=
0
;
for
(
int
i
=
0
;i
<
hwData.Length;i
++
)
{
Y
=
((i
%
2
)
==
0
?
Y2:Y1);
DX
=
hwData[i]
*
dx;
e.Graphics.DrawLine(p, X, Y, X
+
DX, Y);
X
+=
DX;
e.Graphics.DrawLine(p, X, Y1, X, Y2);
}
}
}
}
测试程序运行结果如下:
(上面显示的数据为高电平和低电平的持续时间(低高低高…),单位为1/10毫秒)
注意事项:
1、红外遥控器按键偶数次和奇数次的编码是不同的,程序需要学习两次,才能正常识别按键信息。
2、普通的USB转串口由于仅连接了2、3、5管脚,所以不能正常使用,对比较好的USB转串口(比如Moxa的三百多一根),虽然所有的管脚都引出了,但是由于是通过USB转换的,所以响应时间很是问题,我就因为这个差一点功亏一篑,幸好把程序又在PC机跑了一遍。
//获得DSR的状态
GetCommModemStatus(HSC_COM_Handle,&ModemState);
上面的指令如果采用的是USB转串口,运行时间会是7ms左右,而用主板自带串口仅是几个微秒,相差实在太大了。所以上面的红外接收器程序在没有自带串口的笔记本上是无法正常工作的。
源码下载地址:http://www.sky-walker.com.cn/yefan/SourceCode/YFHSCollectTest.rar