一个简单的串行协议的封装

来源:互联网 发布:rekordbox软件的作用 编辑:程序博客网 时间:2024/06/12 20:33

所有的软件开发人员都会在他的职业历程中设计一个串行口收发单元。这里展示了一个
基于串行口的协议封装实现。大家可以对比一下自己的实现看看。

首先需要对整个软件的结构进行拆分。理论上它应当至少分割为三个大的部分:
1.通讯层。处理bit流的传递。
2.数据帧层。处理frame的析取和打包。
3.应用层。处理各个独立的数据帧。并提供更上层的信息通道。

通讯层最先构建,并可进行独立测试。它又按照内部的逻辑部件,拆分为以下部分:
1.1 ComPort ……. 处理通讯口的IO处理
1.2 RingBuf ……. 提供一个方便的环形缓冲区
1.3 SynObj …….. 提供线程同步用的相关同步对象
1.4 UartThread …. 主执行线程,通讯层的主集成部件

下面给出各个类的接口:

struct ComPort{    char            *uartPort; //串口    HANDLE          fp;    OVERLAPPED      read;    OVERLAPPED      write;    int             lastReadCnt;    int             lastWriteCnt;    int             charTimeOut;    ComPort();    bool Create(char *uartPort=NULL);    void Destory();    bool IsOpen();    bool IsInRecving();    bool IsInSending();    void Recv(void *buf, int len);    void Send(void *buf, int len);    bool CancelIo(bool waitForEver);    int GetLastRecvCount();    int GetLastSendCount();    void ClearLastRecvCount();    void ClearLastSendCount();};struct RingBuf{    unsigned char   *ioBuf; //*ringBuf head    int             ptrPush; //写指针    int             ptrPop; //读指针    int             ringBufSize; //ringBufSize    int             appendTailSize; //物理接缝尾部追加的字节数    RingBuf();    bool Create(int ringBufSize, int appendTailSize);    void Destory();    int GetNextPushSizeMax();    int GetNextPopSizeMax();    int GetCurrentSize();    bool IsFull();    unsigned char *GetPushPos();    unsigned char *GetPopPos(int expectFrameSize);    unsigned char *GetPhysicalTailPos();    int Push(int size);    int Pop(int size);private:    //禁止外部调用    bool MendTail(int expectFrameSize);};class SynObj{public:    HANDLE Handle();    bool Wait(bool waitForever);    void Set();    void Reset();    SynObj();    ~SynObj();private:    HANDLE hEvent;};class UART_DLL_API SwxxUartThread : public TThread{private:    ComPort         &port;    RingBuf         &recvBuf, &sendBuf;    SynObj          &hIo, &hQuit;    list<UART_RECV_LISTENER> listener;protected:    void __fastcall Execute();    void SaveReadData_AndReadMore();    void ClearSendData_AndSendMore();    void NotifyListener(int cnt);public:    static const int MAX_RECV_BUF_SIZE = RECV_FRAME_MAX;    __fastcall SwxxUartThread(char *uartPort, bool CreateSuspended);    virtual __fastcall ~SwxxUartThread();    void RegRecvListener(HWND hWnd, unsigned int msg);    void UnRegRecvListener(HWND hWnd, unsigned int msg);    unsigned char *GetRecvBuf(int needSize);    int popRecvBuf(int size);    int SendFrame(unsigned char *buf, int size);    bool ShutDown();};

值得一提的是其中的RingBuf部分,为了能够方便地读取环绕接缝处的信息,我们在环
形缓冲区的末尾额外添加了一部分内存空间,大小=最大的数据帧长度,以能够使上层
应用可以透明的方式访问这个环形缓冲区。

数据帧层只有两个静态函数:

class LkqProtocol{public:    //接受数据帧的协议解析    //  buf, bufSize是输入参数    //  frameHeader表征了是否识别到数据帧,如果frameHeader!= NULL表示成功地识别    //  到一个数据帧,此时frameLen>0表示帧长度,frameHeader可能并不等于buf.表征    //  识别到的数据帧头部相较原始数据缓冲区头部的偏移量;    frameHeader==NULL    //  时,帧长度可能为负值,表明了识别到了部分一段可能的帧尾部残留,此时用户可    //  将该部分数据帧丢弃。而frame_len == 0表示帧接收尚不完整。当frameLen<>0时    //  用户需要pop(abs(frame_len))长度的数据;    static void AnalyseRawFrame(unsigned char *buf, int bufSize, unsigned char **frameHeader, int *frameLen);    //打包一个数据帧(目前,仅打上CRC码)    static void PackageFrame(unsigned char *frame, int frame_total_len);};

分别用来进行帧析取和帧打包。

最后是应用层:

class UART_DLL_API LkqService{private:    bool isRecvSKQFrame;    list<UART_RECV_LISTENER> listener;    LkqApplicationObj cur;protected:    void FrameItem_DealBreakAuto(int *final, short sample);    void FrameItem_DealBreakAlone(int *final, short sample);    void FrameItem_DealPowerDual(int *final, short sample);    void FrameItem_DealPowerSingle(int *final, short sample);    void FrameItem_DealPowerDual2(int *final, short sample);    //协议帧分析器    void DealFrame(unsigned char *frameHeader, int frameLen);    void DealFrame_SKQ20(unsigned char *frameHeader, int frameLen);    void DealFrame_SKQ21(unsigned char *frameHeader, int frameLen);    void NotifyListener(int wParam, int lParam);public:    LkqService(unsigned char hardwareType = 0x20){isRecvSKQFrame = false; cur.hardwareType = hardwareType;}    //由系统当前速度,得到系统将达到最高速度和加速度值(可+可-)    void GetFinalSpeed(int speed, int *finalSpeed, int *acc);    //注册应用层数据侦听器    void RegServiceListenser(HWND hWnd, unsigned int msg);    //取消注册应用层数据侦听器    void UnregServiceListener(HWND hWnd, unsigned int msg);    //得到原始输入缓冲区中需要丢弃的字节数    int GetPopCntOfRawStream(unsigned char *buf, int bufSize);    //设置指示灯    int SetLamp(unsigned char *buf, int len, int lampIdx, bool isOn);};

应用层的消息通知是利用Windows消息系统实现的。

最终的用户访问代码主要是两个用户自定义消息的处理:

//Com口数据到来处理void __fastcall TForm1::OnComRecv(TMessage &Message){    unsigned char *recv = NULL;    int recvCnt = Message.WParam;    do    {        if(recvCnt > Com1->MAX_RECV_BUF_SIZE) recvCnt = Com1->MAX_RECV_BUF_SIZE;        recv = Com1->GetRecvBuf(recvCnt);        int temp = Lkq->GetPopCntOfRawStream(recv, recvCnt);        if(temp)        {            Com1->popRecvBuf(temp);            recvCnt -= temp;        }        else break;    }while(recv != NULL);}//应用层消息处理void __fastcall TForm1::OnLkqFrame(TMessage &Message){    //略}

对于数据帧发送,可以采用如下代码:

    unsigned char buf[1024];    int frameLen;    frameLen = Lkq->SetLamp(buf, 1024, 0, 1); //这对应一个应用层服务接口    if(frameLen>0) Com1->SendFrame(buf, frameLen); //通过通讯口对象发出bit流    else assert(0);
0 0
原创粉丝点击