Pocket PC 电视遥控器 (源代码) |
作者:Nick Deacon 来源:codeproject |
IntroductionHave you ever wanted to be able to control your TV, Hi-Fi, or Video using the IR port on your pocket PC? Here's how to do it. BackgroundI recently lost the TV remote for my old Sony TV. In itself that was no problem, as I bought a replacement remote which did the job. However, when the TV lost its colour setting, I had a problem as it could only show pictures in black and white, and the replacement remote didn't have the buttons for colour adjustment. I decided to write a program on my old Jornada 525 Pocket PC to send the correct codes to the TV using the IR port. There appears to be three main protocols for sending IR codes to devices. Sony uses the 'Pulse Coded' method which entails sending a steam of data containing header bits, '1' bits and '0' bits separated by spaces. These bits modulate a carrier of 40KHz, and are of different lengths, 2200 us for the header, 110 us for a 1 bit and 550 us for a 0 bit. The spaces are 550 us of silence. Most Sony equipment uses 12 bits of data, which is separated into 6 bits of address (the device type) and 6 bits of command. So the data looks like this: hxxxxxxyyyyyy where h is the header bit, xxxxxx is the 6 bits of the command (msb first) and yyyyyy is the 6 bits of address. I won't go into any further details on this, as there are many sources on the internet that describe the protocol and list the codes for the different devices. Some newer Sony equipment use 19 bit codes, and I believe that other manufacturers use the same format that I have described. It should also be possible to write similar classes for devices that use 'Space Coded' or 'Shift Coded' protocols. I have written a class called Using the codeThe UINT CIrPulse::FindIrPort() { // Look into the registry for the IR port number 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; } Having got the port number, you can call the BOOL CIrPulse::Open(UINT uiPort) { ASSERT(uiPort > 0 && uiPort <= 255); Close(); // Open the IRDA port 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; } // Set the size of input and output buffers VERIFY(SetupComm(m_irPort, 2048, 2048)); // clear the read and write buffers VERIFY(PurgeComm(m_irPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)); // Reinitializes all IRDA port settings 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)); // Set the timeouts for all read and write operations 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; } Call the function Finally call BOOL CIrPulse::SendCode(DWORD lValue) { DWORD dwCount; int i=0; ASSERT(iDataLength>0); //purge the transmit buffer VERIFY(PurgeComm(m_irPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR)); // send the code 6 times for each button press for(int x=0;x<6;x++) { MakeStream(lValue); //send the code dwCount=GetTickCount(); while(GetTickCount() 26) //delay for 26 ms i++; } return true; } Note that this function calls another function The function The function The function works fine on my Jornada, but see the discussion below to see what changes you might have to make. BOOL CIrPulse::MakeStream(DWORD lValue) { DWORD dwStreamLength; //make the start pulse dwStreamLength=iHPulse/charWidth; ASSERT(Write((const char *)bPulseStream.GetData(), I have included a simple application, which uses Points of InterestBecause the The biggest problem is how to achieve the periods of silence which separate each pulse. It is not possible to produce pure silence out of the serial port, as even if you send a 0x0 character, you still get pulses on the IR due to start and stop bits. I experimented with sending different characters on the assumption that if you can send a carrier at frequencies other than 40KHz, this might fool the device into accepting this as a silence. This has the advantage in that you can produce one byteArray containing the data for the entire code, ensuring that the timing is accurate. The results however were not consistent, and I rejected this method in favour of pausing between sending the groups of 0xdb characters out of the serial port. Because the delay needed is in the order of 550 us, there is no way (that I found) of consistently achieving this delay which is independent of processor speed. On my Jornada I didn't need to create a delay at all, as each call to the |