最近看到一篇僅需要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
本文作者:未知