这边文章是转载的,Symbian串行通信的原理是相同的,转载这边文章的原因是,这边文章中包含一些与红外和蓝牙通信的说明。
串行通信是一种用于两台设备间(典型情况下是距离较近)传输数据的低级别点对点技术。Series60支持红外线和蓝牙上的串行通信。Series60实现的中心是串行通信服务器(Serial Communication Server,又称Comms服务器或C32)。它使用Symbian OS客户端/服务器框架,提供对串行硬件的访问,并且是通用的和共享的。通用是指红外线和蓝牙串行通信使用相同的API,共享是指多个客户端线程可以安全地并发使用同一个串行端口。
Series60中所有的串行通信都使用下列基本步骤实现:
1. 装载串行设备驱动器
2. 启动Comms服务器
3. 连接到Comms服务器
4. 装载一个comms模块(又称CSY: “Comms SYstem”)----Comms服务器的插件,它将决定使用哪种类型的串行端口(红外线or蓝牙)
5. 打开一个串行端口
6. 配置此串行端口
7. 从端口读写数据
8. 最后关闭端口
通信过程中会涉及到几个重要的类,下面简单介绍之:
1. RCommServ
串行通信服务器会话类。它描述了同Comms服务器的会话。提供了连接到服务器函数、装载/卸载不同comms模块函数、查询有效端口名字和数量函数等。
同comms服务器间的会话是不可共享的。此类不能被继承。继承自RSessionBase。
Members
Defined in RCommServ:
Connect(), CreateThreadInCommProc(), GetPortInfo(), LoadCommModule(), NumPorts(), RCommServ(), UnloadCommModule(), Version(), __DbgCheckHeap(), __DbgFailNext(), __DbgMarkEnd(), __DbgMarkHeap(), __DbgSetTraceMask()
Inherited from RHandleBase:
Attributes(), Close(), Duplicate(), FullName(), Handle(), HandleInfo(), Name(), SetHandle(), SetHandleNC(), iHandle
Inherited from RSessionBase:
CreateSession(), EAutoAttach, EExplicitAttach, Open(), Send(), SendReceive(), SetReturnedHandle(), ShareAuto(), ShareProtected(), TAttachMode
2. RComm
继承自RSubSessionBase,描述了一个子会话,使用某一个端口同C32服务器通信。提供的函数均通过操作端口来实现通信,包括打开、关闭、读写、端口配置和性能检测等。一旦使用了某端口来通信,就不能再改变此端口。
下面是串行通信步骤的详解:
1. 装载串行设备驱动器
有两个部分需要装载:一个物理驱动器(直接与硬件交互)和一个逻辑驱动器(提供物理驱动器上的API)。它们的名字是固定的,在这里属于全局性定义。注意,用于模拟器生成(WINS)的物理设备驱动器和用于目标生成的不同。
// Physical device driver names
#if defined (__WINS__)
_LIT (KPddName, "ECDRV");
#else
_LIT (KPddName, "EUART1");
#endif
// Logical device driver names
_LIT (KLddName, "ECOMM");
下面是装载驱动器的过程:
TInt err;
#if defined (__WINS__) // File Server required in WINS to enable loading of device drivers
RFs fileServer;
User::LeaveIfError(fileServer.Connect());
fileServer.Close();
#endif
// Load the physical device driver.
err = User::LoadPhysicalDevice(KPddName);
if (err != KErrNone && err != KErrAlreadyExists)
{
User::Leave(err);
}
// Load the logical device driver.
err = User::LoadLogicalDevice(KLddName);
if (err != KErrNone && err != KErrAlreadyExists)
{
User::Leave(err);
}
若装载函数返回KErrAlreadyExists,则说明此驱动器已装载,这不会被视为错误,因此不需要事先检测驱动器是否已装载。如果使用的代码是UI应用程序的一部分,则这些驱动器将已经被Series60的UI框架装载。前面的#ifdefined(__WINS_)的几行代码首先连接一个 RFs会话,然后关闭它,目的是确保调用装载驱动器前已经装载了File服务器。
2. 启动Comms服务器
// Start the comms server process
err = StartC32();
if (err != KErrNone && err != KErrAlreadyExists)
{
User::Leave(err);
}
3. 连接到Comms服务器
RCommServ是Comms服务器的客户端句柄,所有后续代码都将使用它与Comms服务器通信。
// Connect to the Serial comms server.
User::LeaveIfError(iCommServer.Connect());
4. 装载CSY
CSY是Comms服务器的DLL插件,进行数据传输之前需要显式装载它。和驱动器一样,分别使用名字"IRCOMM"和"BTCOMM"装载红外线和蓝牙CSY。
// Comms modules
_LIT (KIrComm, "IRCOMM");
// Load the CSY module.
User::LeaveIfError(iCommServer.LoadCommModule(KIrComm));
5. 打开串行端口
接下来打开一个串行端口。同样,仍然按名字打开它。但注意端口名是不固定的,可能需要动态获取它。不过对于红外线的情况,端口名是公认的。
// Comm Port Name
_LIT (KPortName, "IRCOMM::0");
User::LeaveIfError(iCommPort.Open(iCommServer, KPortName, ECommShared));
RComm::Open()的第三个参数说明打开此端口的模式,可能的取值如下:
ECommExclusive:此端口一旦打开,即不能被其他任意RComm客户端使用
ECommShared:此端口可以被在相同模式下打开的其他RComm客户端共享
ECommPreemptable:如果其他客户端试图打开此端口则会丢失
对于非公开的端口名,下面的代码演示了如何动态获取它:
TSerialInfo portInfo;
// 获取选择CSY所对应的端口信息
User::LeaveIfError(iCommServer.GetPortInfo(KIrComm, portInfo));
// 构建一个描述符,包含该CSY所支持的最低端口
// 要求它的大小足以包含TSerialInfo.iName的值(定义为KMaxPortName,最大值为16个字符)
// 加上两个冒号和1或2个数字
_LIT(KColons, ":: ");
TBuf<KMaxPortName + 4> portName;
portName.Append(portInfo.iName);
portName.Append(KColons);
portName.AppendNum(portInfo.iLowUnit);
// 现在打开第一个串行端口,在此为唯一模式
User::LeaveIfError(iCommPort.Open(iCommServer, portName, ECommExclusive));
6. 配置串行端口
最终使用已打开的串行端口之前,可能需要对它进行配置。这取决于希望连接到的设备的需求。提供串行通信的设备通常会附带某种形式的文档,详细说明需要的配置。同时可以通过查询端口的功能确保它支持需要的配置。
查询端口功能的方法如下:
// Check port capabilities
TCommCaps portCapabilities;
iCommPort.Caps(portCapabilities);
if (((portCapabilities().iRate & KCapsBps19200) == 0) ||
((portCapabilities().iDataBits & KCapsData8) == 0) ||
((portCapabilities().iStopBits & KCapsStop1) == 0) ||
((portCapabilities().iParity & KCapsParityNone) == 0))
User::Leave (KErrNotSupported);
这里使用的所有常数值(如KCapsBps19200)都在系统头文件d32comm.h中定义。此外,如果端口的功能与需求不匹配,并不需要异常退出,可以选择以不同的方式处理此情况,例如提示用户选择不同的端口或CSY。
接下来,可以设置端口的配置。首先应该获取当前配置,这样可以使不希望显式设置的值保持不变:
// Configure port
TCommConfig portSettings;
iCommPort.Config(portSettings); // Get current configuration
// Set port characteristics
portSettings().iRate = EBps19200;
portSettings().iParity = EParityNone;
portSettings().iDataBits = EData8;
portSettings().iStopBits = EStop1;
portSettings().iFifo = EFifoEnable;
portSettings().iHandshake = KConfigObeyXoff|KConfigSendXoff;
portSettings().iTerminator[0] = 10; // line feed character
portSettings().iTerminatorCount = 1;
User::LeaveIfError(iCommPort.SetConfig(portSettings));
最后,有些杂项配置信息不能使用TCommConfig对象设置,例如对于DTR(Data Terminal Ready,数据终端就绪)和RTS(Ready To Send,准备发送)之类的流控制信号,需要使用RComm::SetSignals()设置它们:
// Turn on DTR and RTS
iCommPort.SetSignals(KSignalDTR, 0);
iCommPort.SetSignals(KSignalRTS, 0);
// Set buffer size
const TInt KBufferLength = 4096;
iCommPort.SetReceiveBufferLength(KBufferLength);
7. 在端口上传输数据
// Timeout Value
const TTimeIntervalMicroSeconds32 KTimeOut(4000000);
Cancel();
iDataBuf.Copy(aTxData);
iCommPort.Write(iStatus, KTimeOut, iDataBuf);
SetActive();
有几个重要的地方需要注意:
* 调用Write()之前调用了Cancel(),这是因为Write()是异步的,这样可以确保不会试图同时执行两个写操作,这将导致错误。
* Series60是一个Unicode平台,所以aTxData是一个16位描述符。因此不能把它直接传递到RComm::Write()---该函数需要一个显式的8位描述符,所以首先需要把它复制到一个8位描述符,然后传递到Write()。
* Write()的第二个参数是一个超时值,这里定义为4秒。如果Write()请求未在此时限内完成将会强制结束,并把iStatus设置为KErrTimedOut。
8. 关闭端口
在串行端口上发送和接收数据结束后,必须关闭此端口(或更明确的说,关闭此端口的句柄),同时还要关闭服务器:
iCommPort.Close();
iCommServer.Close();