使用 PDA 的通用遥控器






4.67/5 (21投票s)
2007年5月10日
6分钟阅读

135135

1522
开发“PDA 的通用遥控器”
引言
在结束了技术学院(HTL)第四年的学习后,我们正在寻找一个有趣的项目。我们决定开发自己的“PDA 通用遥控器”。有了这个程序,就可以控制你的电视。我们在互联网研究中,没有找到太多关于这个主题的信息,但我们在 codeproject.com 上找到了一些有用的信息,因此我们决定在这里发布我们自己的解决方案。
关于程序
左键单击 gadget 并拖动以移动它。左键单击 gadget 的右下角并拖动以调整其大小。右键单击 gadget 以访问其属性。
- 适用于 PocketPC(Windows Mobile 3.0 及更高版本)的应用程序
- Windows CE 操作系统
- C# 代码(Visual Studio 2005)
实现需要
- 打开 PDA 上的 COM 端口(COM1、COM2、…)
- 创建要发送到 PDA 的代码(RC5-Code)
- 创建暂停(单位:µs)
传输
PDA 上现有的红外端口使用标准的 IrDA 协议。问题是,虽然 C# 中的 IrDA 类能够打开端口,但需要一个终端才能成功传输。这意味着该类需要连接两个智能设备(PDA-PDA 或 PDA-Handy)。因此,不能使用 IrDA 进行面向连接到电视等哑设备的传输。
红外传输有三种类型
- 标准红外(SIR 115Kbps)
- 快速红外(FIR 4Mbps)
- 消费级红外(CIR 115Kbps)
CIR 是你在遥控器中发现的那种,具有宽广的射程。红外光的波长在 940 到 950 纳米之间。命令如何编码取决于设备的制造商。
我们使用串行端口,并且通常可以基于 UART 协议创建每个代码。第一步是找出 PDA 上的哪个 COM 端口负责 IR 端口。
COM1 | DS 上的 RS232 连接器,如果 DS 处于正常(非透明)模式 |
COM2 | 原始红外,由 IrDA 协议内部使用(如果你真的需要原始 IrDA,请以与 RS232 通信相同的方式使用此端口。) |
COM3 | 由 IrDA 协议内部使用(使用 IrDA 套接字与 IrDA 通信) |
COM4 | 设备中的 GPS 模块 |
COM5 | GSM 模块 |
COM6 | DS 中的 GPS 模块,如果 DS 处于正常状态(只读访问) |
COM7 | 设备上的 RS232 连接器(H41RS 设备上的 Lemo 连接器) |
COM8 | DS 上的 RS232 连接器,如果 DS 处于透明模式 |
COM9 | 免费 |
COM2 端口使得可以通过 IR 端口发送串行数据。C# 中的 SerialPort 类提供了访问串行驱动程序属性的方法。我们编写了一个程序,可以显示 PDA 上所有可用的 COM 端口。最初的问题是只显示了 COM3 端口。解决方案是为 SerialPort 类安装一个更新。该更新称为 Microsoft .NET Compact Framework 2.0 Service Pack 1,它修复了 SerialPort 类中一些有缺陷的方法。
public void GetName()
{
StringBuilder s = new StringBuilder();
String s1;
//get a list of port names
string[] ports = SerialPort.GetPortNames();
//display port names
foreach (string port in ports)
{
s.Append(port+" ");
}
s1 = s.ToString();
label1.Text = "Port Names: "+s1;
}
RC5 代码
接下来的步骤需要 RC5 代码的基础知识。首先,我们必须初始化串行端口的所有参数
//initialize the parameters for the SerialPort object
public static String portName = "COM2";
static int baudRate = 115200;
public static Parity parity = Parity.None;
static int dataBits = 7;
public static StopBits stopBits = StopBits.One;
SerialPort IR_COM = new SerialPort(portName, baudRate, parity,
dataBits, stopBits);
曼彻斯特编码
下一步是初始化参数以生成曼彻斯特编码。在 `RohCodeToManchester()` 中,原始数据数组将被传递给 `ManchesterCode()`。`ManchesterCode()` 将 1 生成为从 1 到 0 的变化,将 0 生成为从 0 到 1 的变化。
//initialize the parameters for the function ManchesterCode and
//RohCodeToManchester
char[] rohData = new char[14] {
'1','1','0','0','0','0','0','0','0','0','0','1','0','0' };
public char[] manchesterData = new char[29];
int index = 0, md = 0;
public void ManchesterCode(char c)
{
if (c == '1')
{ // 1 -> 10
manchesterData[index] = '1';
manchesterData[++index] = '0';
}
if (c == '0')
{ // 0 -> 01
manchesterData[index] = '0';
manchesterData[++index] = '1';
}
index++;
}
public void RohCodeToManchester()
{
for (int i=0; i < rohData.Length; i++)
{ //the array rohData is passed to the function ManchesterCode
ManchesterCode(rohData[i]);
}
//mark the end
manchesterData[28] = 'e';
}
生成突发和暂停
为了确保数据传输安全,会传输 38 kHz 调制的信号。然后信号会被 IR 接收器解调。信号看起来像这样
在接下来的计算中,你必须考虑到 IR 发射器会反转信号,并且 LSB 会首先传输。接下来的步骤将从这个角度进行解释,这同时也是程序的视角。
低位信号会被突发调制,高位信号会是暂停。所以曼彻斯特编码中的每个 0 都会被突发调制,每个 1 代表 889µs 的暂停。我们知道波特率必须是 115 kBaud,并且信号必须以 38 kHz 调制。
26µs 的周期长度对应于 38.4kBaud 的波特率。由于 38.4kBaud 不是标准波特率,我们需要将其提高到 115.2kBaud。一个比特需要 8.7µs。
其中
- Ts 是周期长度
- ts 是一个比特的持续时间
- Vs 是波特率
- f 是频率
我们期望的突发信号应该像 100100100,包括起始位和停止位。这与下面的表示一致。从我们开始的基础 100100100,我们向后计算,并考虑到 IR 发射器会反转信号并首先发送 LSB。
100100100 | 带起始位和停止位 |
0010010 | 不带起始位和停止位 |
1101101 | 反转的 |
1011011 | 先发送 LSB |
0x5B | 十六进制 |
字长设置为 1 个起始位、7 个数据位和 1 个停止位。顶部的图像显示起始位和停止位理想地集成到了突发信号中。因此,一个突发信号需要 79.7µs。因为突发信号代表数据流中的逻辑 0,所以我们需要将突发信号扩展到 RC5 数据长度 889µs。对于一个比特,我们需要发送 11 次突发信号 0x5B。
其中
- tb 是突发的持续时间
- z 是突发的数量
生成暂停的一个合乎逻辑的方法是发送 11 次 0x00,但这并不是解决方案,因为每个字节的停止位都是比特流的一部分。所以我们必须找到一个计时器来创建 889µs 的暂停。有一个名为 QueryPerformanceCounter 的计数器,它是 PDA 上 `coredll.dll` 中的一个函数。通过 QueryPerformanceCounter,我们可以实现精确的时间测量。这是因为 QueryPerformanceCounter 可以直接访问处理器时钟。
在下面的代码中,我们导入 `coredll.dll` 并为 QueryPerformanceCounter 和 `BurstOut` 函数进行初始化。使用 DllImport,你可以导入 `.dll` 文件,之后你可以像在程序中使用普通函数一样使用 `QueryPerformanceCounter()`。名为 BufferBurst 的字节数组包含 22 个 0x5B。这是上面计算的结果,因为对于一个 RC5 位,我们需要发送 11 次 0x5B。由于两个逻辑 0 可能在数据流中连续出现,BufferBurst 的长度是两倍。
>//import the coredll.dll with the functions QueryPerformanceCounter
//and QueryPerformanceFrequency
[DllImport("coredll.dll")]
extern static int QueryPerformanceCounter(ref long perfCounter);
[DllImport("coredll.dll")]
extern static int QueryPerformanceFrequency(ref long frequency);
//initialize the parameters for the QueryPerformanceCounter
long ctrStart = 0, ctrAkt = 0, ctrEnd = 0;
`BurstOut()` 函数将一个 `long` 类型的变量传递给 `QPC()` 方法。这个变量设置了 QueryPerformanceCounter 的时间间隔。在 `QPC()` 中,我们停留在 while 循环中,直到达到指定的时间。
public void QPC(long data)
{ //start the counter
QueryPerformanceCounter(ref ctrStart);
//specified the end time
ctrEnd = ctrStart + data;
while (ctrAkt < ctrEnd)
{ //wait until the counter reaches the end time
QueryPerformanceCounter(ref ctrAkt);
}
}
调制是在 `BurstOut()` 函数中完成的,其中曼彻斯特编码数组通过一个 while 循环传递。在那里,我们检查当前位和下一位。如果比特序列是 10,则会产生 889µs 的暂停;如果比特序列是 11,则等待两倍的时间。当比特序列是 01 时,我们发送 11 次突发信号;当比特序列是 00 时,发送 22 次。
public void BurstOut()
{
while (manchesterData[md] != 'e')
{
//pause of 889µs
if (manchesterData[md] == '1' && manchesterData[md + 1] == '0')
{
QPC(319);
}
//pause of 1,778ms
if (manchesterData[md] == '1' && manchesterData[md + 1] == '1')
{
QPC(766);
md++;
}
//burst of 889µs
if (manchesterData[md] == '0' && manchesterData[md + 1] == '1')
{
IR_COM.Write(bufferBurst, 0, 10);
}
//pause of 1,778ms
if (manchesterData[md] == '0' && manchesterData[md + 1] == '0')
{
IR_COM.Write(bufferBurst, 0, 22);
md++;
}
//the end
if (manchesterData[md] == '0' && manchesterData[md + 1] == 'e')
{
IR_COM.Write(bufferBurst, 0, 10);
}
md++;
}
}
历史
- 2007 年 5 月 10 日 - 发布原始版本