關於CnComm的一點總結

CnComm是國人llbird所編寫的一個串口通信開源庫,詳細介紹請查看:https://blog.csdn.net/wujian53/article/category/336981,最新的討論博客請查看:http://www.cppblog.com/llbird/archive/2009/05/15/CnComm.html,最新的版本爲1.51(事實上已多年沒更新的說。。)。

(PS:月更博客又來了,差點忘記更新了)

今天來討論一下CnComm在windows下的異步讀取和同步讀取用法,這些都只是我在工作上使用這個庫時用法的總結,如有錯誤,請在評論區指出來,謝謝。

首先是異步讀取的用法:

首先我們創建一個CnComm對象,例如

    CnComm cncomm;

然後設置CnComm的屬性並打開串口

    if(!cncomm.open(1,115200))
    {
        //打開串口失敗
    }

上面第一行代碼的作用爲“啓用讀緩衝”,當啓用讀緩衝之後,CnComm會使用線程定時檢查串口是否有數據等待讀取,若串口存在等待讀取的數據時,CnComm就會把數據讀取到對象的緩衝區中,然後通過windows消息ON_COM_RECEIVE通知窗體接收串口數據。第二行代碼的作用爲以115200的波特率打開串口1。

於是我們打開串口之前還要設置CnComm發送ON_COM_RECEIVE的窗體句柄

    cncomm.SetWnd(m_hWnd);
    if(!cncomm.open(1,115200))
    {
        //打開串口失敗
    }

這樣就能在ON_COM_RECEIVE的窗體響應函數中通過調用CnComm的Read函數接收數據了。例如

    char recv_data_buf[100] = { 0, };
    size_t recv_length = cncomm.Read(reav_data_buf, 100);

上述代碼的意思爲通過cncomm對象讀取串口100個數據到recv_data_buf中,函數執行完畢後recv_length中存放的爲Read函數實際讀取到的數據的長度。

下面是同步讀取的方法

首先我們創建一個CnComm對象,例如

    CnComm cncomm;

然後打開串口

    if(!cncomm.open(1,115200))
    {
        //打開串口失敗
    }

上面代碼的作用爲以115200波特率打開串口1。

之後我們就可以直接用CnComm的Read函數來接收數據了

    char recv_data_buf[100] = { 0, };
    size_t recv_length = cncomm.Read(reav_data_buf, 100, 1000);

同步讀取與異步讀取的調用方法是一樣的,差別就在於,同步讀取會阻塞主線程(上面的代碼比異步讀取多了超時的參數),要麼超時,要麼數據讀取操作完成;而異步讀取是CnComm通過消息通知窗體讀取的,所以只要讀取數據的長度不超過緩衝區中已有數據的長度,那麼就不會造成阻塞。

但是在CnComm 1.51的版本中存在着問題,就是同步讀取並且使用重疊IO時,超時時間會無效,或者會讀取錯誤的字節長度。我看了一下代碼,發現問題出現在ReadPort函數如下的這部分代碼中

DWORD dwBegin = GetTickCount(), dwEnd, dwCost, uReadLength, uReadReturn;

uReadLength = Stat.cbInQue > dwLength ? dwLength : Stat.cbInQue;
CN_ASSERT(::ReadFile(hComm_, pBuffer, uReadLength, &uReadReturn, &RO_));
dwReadResult += uReadReturn;

do
{
    if (!::ReadFile(hComm_, (LPBYTE)pBuffer + dwReadResult, 1, &uReadReturn, &RO_))
    {
        if (dwWaitTime > 5 && WaitForSingleObject(RO_.hEvent, dwWaitTime) == WAIT_OBJECT_0)
        {
            dwEnd = GetTickCount();
            dwCost = dwEnd>=dwBegin ? dwEnd-dwBegin : DWORD(-1L)-dwBegin+dwEnd;
            CN_ASSERT(::GetOverlappedResult(hComm_, &RO_, &uReadReturn, FALSE));
            dwWaitTime = dwWaitTime > dwCost ? dwWaitTime-dwCost : 0;
        } 
        else
        {
            CN_ASSERT(::PurgeComm(hComm_, PURGE_RXABORT));
            break;
        }
    }

    dwReadResult += uReadReturn;
}
while (uReadReturn && ++dwReadResult < dwLength);

上述代碼中存在兩個問題,第一,在

    CN_ASSERT(::ReadFile(hComm_, pBuffer, uReadLength, &uReadReturn, &RO_));

這句代碼中,如果已經讀取到足夠字節的數據,但是串口中還存在數據時,由於後面的代碼是do...while循環,會導致多讀取一個字節數據的情況。

第二,在

do
{
    if (!::ReadFile(hComm_, (LPBYTE)pBuffer + dwReadResult, 1, &uReadReturn, &RO_))
    {
        if (dwWaitTime > 5 && WaitForSingleObject(RO_.hEvent, dwWaitTime) == WAIT_OBJECT_0)
        {
            dwEnd = GetTickCount();
            dwCost = dwEnd>=dwBegin ? dwEnd-dwBegin : DWORD(-1L)-dwBegin+dwEnd;
            CN_ASSERT(::GetOverlappedResult(hComm_, &RO_, &uReadReturn, FALSE));
            dwWaitTime = dwWaitTime > dwCost ? dwWaitTime-dwCost : 0;
        } 
        else
        {
            CN_ASSERT(::PurgeComm(hComm_, PURGE_RXABORT));
            break;
        }
    }

    dwReadResult += uReadReturn;
}
while (uReadReturn && ++dwReadResult < dwLength);

上面這個循環中,因爲ReadFile函數和GetOverlappedResult函數中的超時時間由超時結構決定,如果在ReadFile函數和GetOverlappedResult函數中都沒有獲取到串口數據的話,由於while條件只檢測了有沒有讀取到任何數據,卻沒有檢測是否已經超時,所以此時就會退出循環,導致出現超時無效的情況。

於是我把上面的代碼修改爲了下面這個樣子,修復了上面我所說的兩個問題

DWORD dwBegin = GetTickCount(), dwEnd, dwCost, uReadLength, uReadReturn;

uReadLength = Stat.cbInQue > dwLength ? dwLength : Stat.cbInQue;
CN_ASSERT(::ReadFile(hComm_, pBuffer, uReadLength, &uReadReturn, &RO_));
dwReadResult += uReadReturn;

//解決因讀取數據過短,上面ReadFile執行完畢之後數據讀取完畢,卻仍然要執行下面函數的問題
if (dwReadResult >= dwLength)
{
    return dwInCount_ += dwReadResult, dwReadResult;
}

do
{
    if (!::ReadFile(hComm_, (LPBYTE)pBuffer + dwReadResult, 1, &uReadReturn, &RO_))
    {
        if (dwWaitTime > 5 && WaitForSingleObject(RO_.hEvent, dwWaitTime) == WAIT_OBJECT_0)
        {
            dwEnd = GetTickCount();
            dwCost = dwEnd>=dwBegin ? dwEnd-dwBegin : DWORD(-1L)-dwBegin+dwEnd;
            CN_ASSERT(::GetOverlappedResult(hComm_, &RO_, &uReadReturn, FALSE));
            dwWaitTime = dwWaitTime > dwCost ? dwWaitTime-dwCost : 0;
        } 
        else
        {
            CN_ASSERT(::PurgeComm(hComm_, PURGE_RXABORT));
            break;
        }
    }

    dwReadResult += uReadReturn;
}
//解決因uReadReturn返回0導致循環退出,超時無效的問題
while ((dwReadResult < dwLength) && (dwWaitTime != 0));
//while (uReadReturn && ++dwReadResult < dwLength);

事實上串口通信使用異步讀取才是最合理的方式,但是因爲我在做的軟件,用異步讀取的話過於麻煩,所以才使用了同步讀取。

順便在這裏討論一下串口通信讀取數據的方式,參考的博客:https://blog.csdn.net/wujian53/article/details/1409130。

上面的博客,我理解的意思是,首先,串口通信應該定義通信協議,例如協議頭+數據長度+數據+協議尾這樣的方式,於是我們在讀取一幀串口數據的時候,就能採用,讀取協議頭-->判斷協議頭是否正確-->讀取數據長度-->判斷數據長度是否有效-->讀取數據長度所要求的數據-->讀取數據尾-->判斷數據尾是否正確,這種方式來讀取。

以上就是我對CnComm一點總結。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章