串口通信之數據接收處理1


  如果通信物理設備連接如下圖1所示,即計算機有1到多個串口,而每個串口設備下僅僅掛載1個采集器,那么協議就沒必要地址碼,協議可以是:同步頭 + 命令字 + 數據長度 + 數據正文 + 校驗碼。此時各個串口通信是互不相關的。        

             

       接收數據可以采用一個隊列,每當串口有數據,就直接進入數據隊列,另一邊再出隊列,試圖查找一個完整的合法數據包。接收數據時的進出隊可以在一個線程里執行;也可以在兩個線程處理,但得同步隊列。

       下面我貼出我的VC代碼(進出數據隊列在同一個線程):

//MSComm控件消息

void CSSRRDlg::OnOnCommMscommRadio()

{

    int                        nEvent;

 

    nEvent    = m_msCom.GetCommEvent();

    switch(nEvent)

    {

    case 2:            //接收緩沖區有數據

           g_bCommDataRcv = true;     //為了控制數據接收線程

           break;

    case 3:

           break;

    default:

           break;

    }

}

每當串口接收緩沖區有數據時,觸發g_bCommDataRcv = true告訴數據接收線程,可以讀串口數據。

 

下面數據接收線程在開機或用戶控制下啟動,等待數據接收和處理。

UINT CSSRRDlg::FetchRadioComMsgThread(LPVOID p)

{    

       int                        i, flag=0;

       unsigned char  tmpQBuf[200];

       CSSRRDlg            *pThis = (CSSRRDlg *)p;

 

       BOOL                   bHead = FALSE;

       int                        Head;

       UINT                    iCount;

       int                        getLen;

       int                        f, r, len, max, tt;

 

       int                        test;

       while(pThis->rcvComData)   //一直成立,不停檢測隊列

       {

              if (pThis->nExitCode_Radio == 1)       //主線程控制此線程退出標識

              {

                     ::AfxEndThread(pThis->nExitCode_Radio);

              }

              //從串口讀數據到隊列--檢測同步頭

              if (pThis->g_bCommDataRcv)      //OnOnCommMscommRadio事件設置的標識

              {

                     pThis->ReadRadioCommData(pThis->hBuf);      //讀串口緩沖區當前所有數據到全局(字節)隊列

                     pThis->g_bCommDataRcv = false;

              }

              //檢測同步頭

              bHead = FALSE;

              getLen = 0;

              iCount = 0;

              int          bOne=TRUE;

 

              while (pThis->hBuf->GetLength() > 4)        //從全局數據隊列hBuf查找一個完整的合法數據包

              {

                     len=pThis->hBuf->GetLength();   //

 

                     f = pThis->hBuf->front;

                     r = pThis->hBuf->rear;

                     max = pThis->hBuf->maxSize;

 

                     tt = (f+1)%max;

                    //驗證數據包的同步頭

         if (pThis->hBuf->GetElement(tt)==0x5D && pThis->hBuf->GetElement(tt+1)==0x5D             

                            && pThis->hBuf->GetElement(tt+2)==0x5D && pThis->hBuf->GetElement(tt+3)==0x02)

                     {

                            bHead = TRUE;

                            Head = tt-1;    //記錄同步頭front位置

                            iCount = 0;

                     }

 

                     //防止多個合法的數據包,只取最后一個-----包內又可能新同步頭,重新計算

                     if (bOne && bHead)

                     {

                            if (pThis->hBuf->GetElement(Head+1+4)==0x01)     //命令字1

                            {

                                   getLen = 86;

                                   bOne = FALSE;

                            }

                            else if (pThis->hBuf->GetElement(Head+1+4)==0x54) //命令字2

                            {

                                   getLen = 7;

                                   bOne = FALSE;

                            }

                     }

                     if (iCount > (UINT)(getLen-1 -4))

                     {

                            if (pThis->hBuf->GetElement(Head+getLen)!=0x03)  //尾不對

                            {

                                   pThis->hBuf->DeSqueue(tmpQBuf, 4);       //剔除此次不完整包的同步頭5d 5d 5d 02

                                   bHead = FALSE;

                                   iCount = 0;

 

                                   bOne = TRUE;

                                   pThis->hBuf->front = Head+4;           

                                   continue;

                            }

                            else  //包完整

                            {

                                   break;

                            }

                     }

 

                     iCount++;       //隊列同步頭檢測移動了一步

                     pThis->hBuf->front++;

              }

              if (bHead && getLen)

              {

                     pThis->hBuf->front = Head;

                     memset(tmpQBuf, 0, sizeof(tmpQBuf));

                     if ( (test=pThis->hBuf->DeSqueue(tmpQBuf, getLen)) == getLen)  //出接收數據隊列

                     {

                            pThis->CheckRadioCommData(tmpQBuf, getLen);            //進一步檢查數據合法性,並(通知界面)處理

                     }

                     else

                     {

                            Sleep(20);

                     }

              }

       }

       return 0;

}

 本程序把協議同步頭 + 命令字 + 數據長度 + 數據正文 + 校驗碼中的校驗碼改成了同步尾,因為我的正文數據80字節,如果用CRC之類校驗太耗時了。

上面代碼的CheckRadioCommData函數就不貼了,涉及具體的業務處理,沒必要貼出來。

//具體實現從串口m_msCom.GetInput數據

void CSSRRDlg::ReadRadioCommData(BtyeSqu *squ)

{

       VARIANT                    vResponse;

       int                        len, i;

       unsigned char  buf[200];

       COleSafeArray      Oledata;

       BYTE                   data[200];

 

       len =m_msCom.GetInBufferCount();          //獲取串口接收緩沖區數據字節數

       if (len > 0)

       {

              vResponse = m_msCom.GetInput();     //讀取緩沖區數據

              Oledata = vResponse;

              len = Oledata.GetOneDimSize();

 

              for(i=0; i<len; i++)

              {

                     Oledata.GetElement((long *)&i, data+i);

              }

              for(i=0; i<len; i++)

              {

                     char a = *(char *)(data+i);

                     buf[i] = (unsigned char)a;

              }

 

              //串口數據進入字符隊列

              if( !squ->IsFull() )

              {

                     squ->EnSqueue(buf, len);

              }

       }

}

 

 

  不使用用戶自定義線程,而采用DataReceive事件,C#代碼基本如下:

  void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (Closing) return;//如果正在關閉,忽略操作,直接返回,盡快的完成串口監聽線程的一次循環
            try
            {
                Listening = true;//設置標記,說明我已經開始處理數據,一會兒要使用系統UI的。
                int n = comm.BytesToRead;//先記錄下來,避免某種原因,人為的原因,操作幾次之間時間長,緩存不一致
                byte[] buf = new byte[n];//聲明一個臨時數組存儲當前來的串口數據
                received_count += n;//增加接收計數
                comm.Read(buf, 0, n);//讀取緩沖數據

                /////////////////////////////////////////////////////////////////////////////////////////////////////////////
                //<協議解析>
                bool data_1_catched = false;//緩存記錄數據是否捕獲到
                //1.緩存數據
                buffer.AddRange(buf);
                //2.完整性判斷
                while (buffer.Count >= 4)//至少要包含頭(2字節)+長度(1字節)+校驗(1字節)
                {
                    //請不要擔心使用>=,因為>=已經和>,<,=一樣,是獨立操作符,並不是解析成>和=2個符號
                    //2.1 查找數據頭
                    if (buffer[0] == 0xAA && buffer[1] == 0x44)
                    {
                        //2.2 探測緩存數據是否有一條數據的字節,如果不夠,就不用費勁的做其他驗證了
                        //前面已經限定了剩余長度>=4,那我們這里一定能訪問到buffer[2]這個長度
                        int len = buffer[2];//數據長度
                        //數據完整判斷第一步,長度是否足夠
                        //len是數據段長度,4個字節是while行注釋的3部分長度
                        if (buffer.Count < len + 4) break;//數據不夠的時候什么都不做
                        //這里確保數據長度足夠,數據頭標志找到,我們開始計算校驗
                        //2.3 校驗數據,確認數據正確
                        //異或校驗,逐個字節異或得到校驗碼
                        byte checksum = 0;
                        for (int i = 0; i < len + 3; i++)//len+3表示校驗之前的位置
                        {
                            checksum ^= buffer[i];
                        }
                        if (checksum != buffer[len + 3]) //如果數據校驗失敗,丟棄這一包數據
                        {
                            buffer.RemoveRange(0, len + 4);//從緩存中刪除錯誤數據
                            continue;//繼續下一次循環
                        }
                        //至此,已經被找到了一條完整數據。我們將數據直接分析,或是緩存起來一起分析
                        //我們這里采用的辦法是緩存一次,好處就是如果你某種原因,數據堆積在緩存buffer中
                        //已經很多了,那你需要循環的找到最后一組,只分析最新數據,過往數據你已經處理不及時
                        //了,就不要浪費更多時間了,這也是考慮到系統負載能夠降低。
                        buffer.CopyTo(0, binary_data_1, 0, len + 4);//復制一條完整數據到具體的數據緩存
                        data_1_catched = true;
                        buffer.RemoveRange(0, len + 4);//正確分析一條數據,從緩存中移除數據。
                    }
                    else
                    {
                        //這里是很重要的,如果數據開始不是頭,則刪除數據
                        buffer.RemoveAt(0);
                    }
                }
                //分析數據
                if (data_1_catched)
                {
                    //我們的數據都是定好格式的,所以當我們找到分析出的數據1,就知道固定位置一定是這些數據,我們只要顯示就可以了
                    string data = binary_data_1[3].ToString("X2") + " " + binary_data_1[4].ToString("X2") + " " +
                        binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +
                        binary_data_1[7].ToString("X2");
                    //更新界面
                    this.Invoke((EventHandler)(delegate { txData.Text = data; }));
                }
                //如果需要別的協議,只要擴展這個data_n_catched就可以了。往往我們協議多的情況下,還會包含數據編號,給來的數據進行
                //編號,協議優化后就是: 頭+編號+長度+數據+校驗
                //</協議解析>
                /////////////////////////////////////////////////////////////////////////////////////////////////////////////

                builder.Clear();//清除字符串構造器的內容
                //因為要訪問ui資源,所以需要使用invoke方式同步ui。
                this.Invoke((EventHandler)(delegate
                {
                    //判斷是否是顯示為16禁止
                    if (checkBoxHexView.Checked)
                    {
                        //依次的拼接出16進制字符串
                        foreach (byte b in buf)
                        {
                            builder.Append(b.ToString("X2") + " ");
                        }
                    }
                    else
                    {
                        //直接按ASCII規則轉換成字符串
                        builder.Append(Encoding.ASCII.GetString(buf));
                    }
                    //追加的形式添加到文本框末端,並滾動到最后。
                    this.txGet.AppendText(builder.ToString());
                    //修改接收計數
                    labelGetCount.Text = "Get:" + received_count.ToString();
                }));
            }
            finally
            {
                Listening = false;//我用完了,ui可以關閉串口了。
            }
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM