【代码核心结构】
最通常想到的思路是,发送一个at命令,然后接收串口返回的响应数据,进而对数据进行解析,这个模型的优点是够简单,不少单片机程序会这样设计。缺点是整个过程是阻塞的,即一次只能处理一个命令,下一个命令必须等待上一个命令处理完毕。
阻塞行为特性在多任务系统中不被接受,因为会使得其他任务都进入无必要的阻塞,为了解决这个缺点,很容易想到应该设计一个队列Queue,这样客户端程序只需要把命令按先后顺序压入队列之中即可,异步等待回调或通知。这种模型下,有一个发送线程监控着队列,将队列的命令逐个发送给串口;另一个接收线程负责接收串口数据,这样一个异步模型就初步建立了。
【A.发送一条命令】
如前所述,发送命令给串口,转换成发送到队列。
QueueCmdWithTimeOut(pCmd)
{
// 将命令pCmd压入g_pCmdQ队列,如果队列满,则等待。
g_pCmdQ->Put(pCmd, INFINITE);
}
【B.发送线程】
CommandThread()
{
// 从队列g_pCmdQ中按顺序取一条命令pCmd,如果没有命令则等待。
g_pCmdQ->Get(pCmd, INFINITE);
// 将命令发送到串口,并处理响应
SendRILCmdHandleRsp(pCmd);
}
这样,当有多命令时候,命令送到队列即可,除非队列满了,否则发送命令的客户端不会阻塞,阻塞的仅仅是发送线程。
【C.接收线程】
ResponseThread()
{
// 从串口读数据到缓存szData.
Read(szData);
// 处理接收到的数据
HandleRxData(szData);
}
【接收线程对数据的处理】
1.接收线程在处理时候要注意串行数据接收的特性,有时候响应并没有完整的接受到,而是接收一半。所以HandleRxData面对无法解析的返回数据时候不能直接抛弃数据,而是继续接收数据append到之前的数据,待下一次来解析。
2.在我们描述的模型中,控制Modem的流程是发一个命令,得一个响应,但是还有很多例外。Modem也会主动发送一些非预期的响应,比如来电通知响应。因此接收线程也要担负部分解析的工作,这在在面HandleRxData伪代码中的AppendString实现。
HandleRxData(szData)
{
// 将接收到的数据append到一个全局缓冲A,A中可能有上一次剩下的数据
AppendReadBytes(szData);
// GiveUpReadBytes把A中的数据取出到szAppend,长度是cbAppend
while(GievUpReadBytes(szAppend, cbAppend)) {
// 如果A没有数据,代表处理完毕,退出循环
if (!cbAppend ) break;
// 处理szAppend里面的数据,剩下些无法处理数据szRemainder。
pRsp->AppendString(szAppend, szRemainder);
// szRemainder=0 代表已经无法解析 把剩下数据退回到A 退出循环
if (!szRemainder) {
InheritReadBytes(); // 剩下数据退回A
break;
} else {
// 对当前一条响应的解析已经完毕
// 根据响应的属性,Unsolicited? Unreconized? 和Notify?
// 做处理,将响应压入g_pRspQ队列中。
if (need) g_pRspQ->Put(pRsp);
AppendReadBytes(szRemainder); // 剩下数据退回A
}
}
}
Unreconfized就是无法识别的响应,Unsolicited是非预期的响应。
【发送线程等待响应】
在发送线程中使用SendRILHandleRsp来发送at命令到串口,如同函数名,这个函数还负责处理本命令的response。问题来了,这个response从何获得? 接收线程在处理response完毕后会把它压入另外一个全局队列中g_pRspQ.
SendRILHandleRsp()
{
WriteCmdsToComPort(szCmd);
g_pRspQ->Get(pRsp, dwTimeout);
HandleRsp(pRsp);
}
队列的Put和Get是同步的,因此看出,在发送和接收线程存在同步机制。
下面是稍微详细一点的代码
CRilHandle::StartInit()
{
m_pInstances = new CDblList<CRilInstanceHandle>;
m_pComDevice = COM_OpenInternal();
m_pComDevice->InitComPortForRIL(NULL, NULL); // 串口的设置
LaunchThreads(); // 启动发送和接收线程
SendComInitString(0); // 发送一串初始命令
}
CRilHandle::LaunchThreads()
{
g_pCmdQ = new CPriorityQueue<CCommand, 40>;
g_pRspQ = new CQueue<CResponse, 10>;
g_pSimLockedQueue = new CQueue<CCommand, 10>;
CreateThread(CmdThreadProc);
CreateThread(ReadThreadProc);
m_pCRilNdis->NdisStart(m_hCancelEvent);
}
DWORD CRilHandle::CommandThread()
{
While(1) {
WaitForCommandOrCancel(); // 等待命令
g_pCmdQ->Get(pCmd, INFINITE); // 得到下一个命令
m_pComDevice->SendRILCmdHandleRsp();
if (m_pComDevice->FDataMode()) // 如果在数据传输模式
// 退出数数据传输模式
}
}
DWORD CRilHandle::ResponseThread()
{
While(1) {
WaitCommEvent(&dwMask); // 等待串口事件.
// 这里会使用PDD的全局参数dwDefaultCOMMask来决定等待哪些串口事件
if (dwMask & EV_RXCHAR) // 串口接收通知
ReadFile(szData, dwRead); // 读取串口数据
HandleRxData(szData, dwRead, false); // 处理接收到的数据
}
}