程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++實現用掌上電腦遙控電視

C++實現用掌上電腦遙控電視

編輯:C++入門知識
1. 簡介
  
  你是否曾想過通過你的掌上電腦上的IR端口控制你的TV、Hi-Fi或者其它視頻?本文將介紹怎樣使用掌上電腦中的IR端口來編程控制一台TV。 <!-- frame contents --> <!-- /frame contents --> 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上控制索尼及其相匹配設備的功能。估計它能夠與其它相匹配設備和操作系統一起工作,但是你需要試驗才行!
  
更多內容請看C/C++技術專題  電腦配置手冊  電腦維修專題專題,或
  3. 實現過程分析
  
  這個CIrPulse類暴露了幾個函數,它們使得發送IR代碼盡可能輕易。在聲明CIrPulse類時,你應該調用一次FindIrPort(),它返回一個描述IrDA端口的端口號的UINT,這通過搜索注冊表得到。 <!-- frame contents --> <!-- /frame contents --> 這個端口號用於後面的調用來打開IrDA端口進行串行通訊。
  UINT CIrPulse::FindIrPort()
  {
   // 查詢注冊表中的IR端口號
   HKEY hKey = NULL;
   if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,_T("CommIrDA"),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_TXABORTPURGE_RXABORT
   PURGE_TXCLEARPURGE_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;
  }
更多內容請看C/C++技術專題  電腦配置手冊  電腦維修專題專題,或
  調用函數SetCodeSize(DWORD)來設置要傳送的位數(如12位)。這可以在任何時候完成且只需要做一次。它一直保持有效,直到後面的調用改變它為止。
  
  最後調用SendCode(long),傳遞實際要發送的代碼。
  BOOL CIrPulse::SendCode(DWORD lValue)
  {
   <!-- frame contents --> <!-- /frame contents -->  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。
  
更多內容請看C/C++技術專題  電腦配置手冊  電腦維修專題專題,或
  這個函數在我的ornada上運行良好。請看下面的討論以進一步確定你要做的可能性改變。
  BOOL CIrPulse::MakeStream(DWORD lValue) {
   DWORD dwStreamLength;
   //創建開始脈沖
   dwStreamLength=iHPulse/charWidth;
   <!-- frame contents --> <!-- /frame contents -->  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函數看上去都使用了合適的時限。不管怎樣,我擔心的是,你可能胡亂產生一個可以使你的掌上電腦能工作的一個延遲。
更多內容請看C/C++技術專題  電腦配置手冊  電腦維修專題專題,或
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved