WinCE下用C++實現掌上型電腦遙控TV

來源:互聯網
上載者:User
 WinCE下用C++實現掌上型電腦遙控TV

1. 簡介

  你是否曾想過通過你的掌上型電腦上的IR連接埠控制你的TV、Hi-Fi或者其它視頻?本文將介紹怎樣使用掌上型電腦中的IR連接埠來編程式控制制一台TV。

  2. 背景

  我近些日子丟失了我的老式索尼TV的遙控器。這本身沒有什麼問題,因為我買了個新的遙控器作為代替。然而,當電視失去了它的設定的顏色時,我遇到了問題,因為它只能顯示黑白色了,而新的遙控器沒有顏色調整按鈕。我決定在我的老式的Jornada 525掌上型電腦上寫一個程式使用IR連接埠把正確的代碼發送給TV。

  共有三個主要協議可以用於發送IR代碼到裝置上。索尼TV使用 ’Pulse Coded’ 方法,它需要發送一個包含頭(header)位的以空格隔開的’1’位和’0’位的資料流。這些位被調製成一種40KHz的載波訊號。其中,頭長度為2200 μs,’1’位為110 μs,’0’位為550 μs,而空格是550μs的沉默(silence)。大多數索尼裝置使用12位元據,它被分離成6位的地址(裝置類型)和6位命令。因此資料看起來象這個樣子:hxxxxxxyyyyyy,其中h是頭位,xxxxxx是6位的命令(msb first),yyyyyy是6位的地址。對此我不再細述,因為網上有很多資源描述這種協議,並列舉了針對不同裝置的代碼。一些新的索尼裝置使用19位代碼,我相信另外的製造商也使用和我描述的相同的格式。還有可能為使用’Space Coded’或’Shift Coded’協議的裝置寫出相似的類。

  我曾使用嵌入式C++寫過一個類CirPulse,它封裝了從一台運行Windows CE 3.0的Jornada 525 PC上控制索尼及其相匹配裝置的功能。估計它能夠與其它相匹配裝置和作業系統一起工作,但是你需要實驗才行!

  3. 實現過程分析

  這個CIrPulse類暴露了幾個函數,它們使得發送IR代碼儘可能容易。在聲明CIrPulse類時,你應該調用一次FindIrPort(),它返回一個描述IrDA連接埠的連接埠號碼的UINT,這通過搜尋註冊表得到。這個連接埠號碼用於後面的調用來開啟IrDA連接埠進行串列通訊。

UINT CIrPulse::FindIrPort()
{
 // 查詢註冊表中的IR連接埠號碼
 HKEY hKey = NULL;
 if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("Comm//IrDA"),0, 0, &hKey) == ERROR_SUCCESS)
 {
  DWORD dwType = 0;
  DWORD dwData = 0;
  DWORD dwSize = sizeof(dwData);
  if (RegQueryValueEx(hKey, _T("Port"), NULL, &dwType, (LPBYTE) &dwData, &dwSize) == ERROR_SUCCESS)
  {
   if (dwType == REG_DWORD && dwSize == sizeof(dwData))
   {
    RegCloseKey(hKey);
    return (UINT) dwData;
   }
  }
  RegCloseKey(hKey);
 }
 return 0;
}

  得到連接埠號碼後,你可以調用Open(UINT)函數,把通過調用FindIrPort()得到的連接埠號碼傳遞過去。這開啟該連接埠並設定串口參數,如果成功返回true。該連接埠被設定為115200傳輸速率,8個資料位元,2個停止位和同位位元。關於如何產生載波以及為什麼我使用這些設定將在本文後面介紹。

BOOL CIrPulse::Open(UINT uiPort)
{
 ASSERT(uiPort > 0 && uiPort <= 255);
 Close();
 //開啟IRDA連接埠
 CString strPort;
 strPort.Format(_T("COM%d:"), uiPort);
 m_irPort = CreateFile((LPCTSTR) strPort, GENERIC_READ | GENERIC_WRITE,0, NULL, OPEN_EXISTING, 0, NULL);
 if (m_irPort == INVALID_HANDLE_VALUE)
 {
  return FALSE;
 }
 //設定輸入和輸出緩衝區的大小
 VERIFY(SetupComm(m_irPort, 2048, 2048));
 //清除讀和寫緩衝區
 VERIFY(PurgeComm(m_irPort,PURGE_TXABORT|PURGE_RXABORT|
 PURGE_TXCLEAR|PURGE_RXCLEAR));
 //重新初始化所有的IRDA連接埠設定
 DCB dcb;
 dcb.DCBlength = sizeof(DCB);
 VERIFY(GetCommState(m_irPort, &dcb));
 dcb.BaudRate = CBR_115200;
 dcb.fBinary = TRUE;
 dcb.fParity = TRUE;
 dcb.fOutxCtsFlow = FALSE;
 dcb.fOutxDsrFlow = FALSE;
 dcb.fDtrControl = DTR_CONTROL_DISABLE;
 dcb.fDsrSensitivity = FALSE;
 dcb.fTXContinueOnXoff = FALSE;
 dcb.fOutX = FALSE;
 dcb.fInX = FALSE;
 dcb.fErrorChar = FALSE;
 dcb.fNull = FALSE;
 dcb.fRtsControl = RTS_CONTROL_DISABLE;
 dcb.fAbortOnError = FALSE;
 dcb.ByteSize = 8;
 dcb.Parity = EVENPARITY;
 dcb.StopBits = TWOSTOPBITS;
 VERIFY(SetCommState(m_irPort, &dcb));
 //為所有的讀和寫操作設定逾時值
 COMMTIMEOUTS timeouts;
 VERIFY(GetCommTimeouts(m_irPort, &timeouts));
 timeouts.ReadIntervalTimeout = MAXDWORD;
 timeouts.ReadTotalTimeoutMultiplier = 0;
 timeouts.ReadTotalTimeoutConstant = 0;
 timeouts.WriteTotalTimeoutMultiplier = 0;
 timeouts.WriteTotalTimeoutConstant = 0;
 VERIFY(SetCommTimeouts(m_irPort, &timeouts));
 DWORD dwEvent=EV_TXEMPTY;
 SetCommMask(m_irPort,dwEvent);
 return TRUE;
}

  調用函數SetCodeSize(DWORD)來設定要傳送的位元(如12位)。這可以在任何時候完成且只需要做一次。它一直保持有效,直到後面的調用改變它為止。

  最後調用SendCode(long),傳遞實際要發送的代碼。

BOOL CIrPulse::SendCode(DWORD lValue)
{
 DWORD dwCount;
 int i=0;
 ASSERT(iDataLength>0);
 //清除傳送緩衝區
 VERIFY(PurgeComm(m_irPort,PURGE_TXABORT| PURGE_RXABORT |PURGE_TXCLEAR | PURGE_RXCLEAR));
 //每次按鍵設定代碼6次
 for(int x=0;x<6;x++) {
  MakeStream(lValue); //發送代碼
  dwCount=GetTickCount();
  while(GetTickCount()<dwCount+26) //延遲26ms
   i++;
 }
 return true;
}

  注意這個函數調用另外一個函數MakeStream(long)6次,每兩次調用之間停頓26毫秒。我發現該代碼必鬚髮送好幾次才能使接收裝置響應,大概是為防止假行為的緣故吧。26毫秒對於接收裝置登記該代碼是必需的,在下一個代碼出現之前。

  這個函數MakeStream(long)把位元組流寫入IrPort,並根據是否有起始位(1或者0)來確保發送正確的資料包長度。包含資料位元組(0xdb)的緩衝區是以一個ByteArray形式存在的。

  函數Close()用於在連接埠使用後,自然地關閉IrPort。

  這個函數在我的ornada上運行良好。請看下面的討論以進一步確定你要做的可能性改變。

BOOL CIrPulse::MakeStream(DWORD lValue) {
 DWORD dwStreamLength;
 //建立開始脈衝
 dwStreamLength=iHPulse/charWidth;
 ASSERT(Write((const char *)bPulseStream.GetData(),
 dwStreamLength)==dwStreamLength);
 // ************************************
 // ***** 在下一個脈衝到來前延遲一段時間
 // ************************************
 //迴圈作業碼中的位來發送脈衝
 for(int i=0;i<iDataLength;i++) {
  if(lValue & 1) {
   //建立一個脈衝1
   dwStreamLength=i1Pulse/charWidth;
   ASSERT(Write((const char *)bPulseStream.GetData(),
   dwStreamLength)==dwStreamLength);
   // *********************************
   // ***在下一個脈衝到來前延遲一段時間
   // *********************************
  }
  else {
   //建立一個脈衝 0
   dwStreamLength=i0Pulse/charWidth;
   ASSERT(Write((const char *)bPulseStream.GetData(),
   dwStreamLength)==dwStreamLength);
   // ********************************
   // **在下一個脈衝到來前延遲一段時間
   // ********************************
  }
  lValue >>= 1;
 }
 return TRUE;
}

  我在所附原始碼中包含了一個簡單的應用程式,它使用CIrPulse來建立一台索尼TV的遠距離遙控。它具有基本的頻道選擇、音量調整和開/關機的功能。

  4. 特別注意

  因為該CIrPort類使用一個序列埠串連到該IR連接埠,所以必鬚生成一個40KHz的載波訊號,這通過從該序列埠發送恰當的字元來實現。幸好,如果我們發送字元0xdb,以115200傳輸速率,用8個資料位元,2個停止位和同位,這樣就能產生一種極接近38.4KHz的載波訊號。我們所有的索尼裝置接收這種資料是沒有問題的。

  最大的問題是,如何?間隔每次脈衝的沉默周期。不可能由序列埠來產生該沉默周期,因為就算你發送一個0x0字元,由於存在起始和停止位,你仍然在該IR上得到脈衝。我通過發送不同的字元進行實驗,依據的前提是如果你不以40KHz的頻率發送一個載波訊號,這有可能使裝置誤把這個當作一個沉默。這樣做的優點是你可以產生一個包含完整的代碼的byteArray,以確保準確計時。但是結果並不一致,所以我拒絕使用這個方法,為的是實現在兩次從序列埠發出成組的0xdb字元之間支援暫停。因為需要的延遲是以550μs的順序;到目前為止,我還沒有找到取得獨立於處理器速度的暫停方法。在我的Jornada上,是完全不必產生一個延遲的,因為每次調用Write函數看上去都使用了合適的時限。不管怎樣,我擔心的是,你可能胡亂產生一個可以使你的掌上型電腦能工作的一個延遲。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.