boost网络串口通信库
一、前言
前面我寫了一篇《boost開發網絡庫》一文,該文章介紹了使用boost庫開發一個高效、穩定的網絡通信庫,其中用到了c++準標準庫boost的asio網絡通信模塊,本文將要講的是使用boost開發usb串口,正好也用到了asio,我之前文章中說過asio不僅僅包含網絡通信,還包括串口,接下來我將帶大家講解使用boost庫實現串口的通信。(當然,我們完全可以使用windows本地api實現類似功能)
串口是一種非常通用的設備通信的協議(不要與通用串行總線Universal Serial Bus(USB)混淆)。大多數計算機包含兩個基于RS232的串口。串口同時也是儀器儀表設備通用的通信協議;很多GPIB兼容的設備也帶有RS-232口。同時,串口通信協議也可以用于獲取遠程采集設備的數據。
串口通信的概念非常簡單,串口按位(bit)發送和接收字節。盡管比按字節(byte)的并行通信慢,但是串口可以在使用一根線發送數據的同時用另一根線接收數據。它很簡單并且能夠實現遠距離通信。比如IEEE488定義并行通行狀態時,規定設備線總長不得超過20米,并且任意兩個設備間的長度不得超過2米;而對于串口而言,長度可達1200米。
典型地,串口用于ASCII碼字符的傳輸。通信使用3根線完成:(1)地線,(2)發送,(3)接收。由于串口通信是異步的,端口能夠在一根線上發送數據同時在另一根線上接收數據。其他線用于握手,但不是必須的。串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對于兩個進行通行的端口,這些參數必須匹配。
首先,我們要了解串口通信的幾個概念:
-
波特率
這是一個衡量符號傳輸速率的參數。它表示每秒鐘傳送的符號的個數。例如300波特表示每秒鐘發送300個符號。當我們提到時鐘周期時,我們就是指波特率,例如如果協議需要4800波特率,那么時鐘是4800Hz。這意味著串口通信在數據線上的采樣率為4800Hz。通常電話線的波特率為14400,28800和36600。波特率可以遠遠大于這些值,但是波特率和距離成反比。高波特率常常用于放置的很近的儀器間的通信,典型的例子就是GPIB設備的通信。 -
數據位
這是衡量通信中實際數據位的參數。當計算機發送一個信息包,實際的數據不會是8位的,標準的值是5、6、7和8位。如何設置取決于你想傳送的信息。比如,標準的ASCII碼是0~127(7位)。擴展的ASCII碼是0~255(8位)。如果數據使用簡單的文本(標準 ASCII碼),那么每個數據包使用7位數據。每個包是指一個字節,包括開始/停止位,數據位和奇偶校驗位。由于實際數據位取決于通信協議的選取,術語“包”指任何通信的情況。 -
停止位
用于表示單個包的最后一位。典型的值為1,1.5和2位。由于數據是在傳輸線上定時的,并且每一個設備有其自己的時鐘,很可能在通信中兩臺設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,并且提供計算機校正時鐘同步的機會。適用于停止位的位數越多,不同時鐘同步的容忍程度越大,但是數據傳輸率同時也越慢。 -
奇偶校驗位
在串口通信中一種簡單的檢錯方式。有四種檢錯方式:偶、奇、高和低。當然沒有校驗位也是可以的。對于偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對于偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位為1,這樣就有3個邏輯高位。高位和低位不是真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步。
當我們打開串口的時候就要指定通信的波特率、數據位、停止位、奇偶校驗位等參數,它是通過核心的類serial_port實現的
二、實現
接下來,我們就用boost的asio實現串口的通信,因為boost庫是跨平臺的庫,所以,我們只需稍加改造就可以運行在linux下。
首先,我的目的很簡單,就是實現串口連接、關閉、發送數據、接收數據,所以我的接口也是這幾個功能:
// ------------------------------------------------------------------------------// Summary:// open com port// Parameters:// [in]port : com port value as SERIAL_PORT specified// [in]baut_rate : baut rate// [in]parity : parity// [in]stop_bit : stop bit// [in]data_bit : data bit length// [out]lHandle : if success return handle on the com port else return the value < 0// Return:// SERIAL_RETURN as description// ------------------------------------------------------------------------------SERIALPORT_API SERIAL_RETURN OpenSerialPort(SERIAL_PORT port, BAUT_RATE baut_rate, PARITY_TYPE parity, STOP_BIT_TYPE stop_bit, DATA_BIT_TYPE data_bit, long* lHandle);// ------------------------------------------------------------------------------// Summary:// register data callback// Parameters:// [in]lHandle : OpenSerialPort returned// [in]pCallBack : data callback// [in]pContext : user context// Return:// NULL// ------------------------------------------------------------------------------SERIALPORT_API SERIAL_RETURN SetDataCallBack(long lHandle, ReadCallBack pCallBack, void* pContext);// ------------------------------------------------------------------------------// Summary:// register disconnect callback// Parameters:// [in]lHandle : OpenSerialPort returned// [in]pCallBack : data callback// [in]pContext : user context// Return:// NULL// ------------------------------------------------------------------------------SERIALPORT_API SERIAL_RETURN SetDisconnectCallBack(long lHandle, DisConnectCallBack pCallBack, void* pContext);// ------------------------------------------------------------------------------// Summary:// close com port// Parameters:// [in]lHandle : OpenSerialPort returned// Return:// NULL// ------------------------------------------------------------------------------SERIALPORT_API void CloseSerialPort(long lHandle);// ------------------------------------------------------------------------------// Summary:// close com port// Parameters:// [in]lHandle : OpenSerialPort returned// [in]pData : send data content// [in]nLen : send data size// Return:// NULL// ------------------------------------------------------------------------------SERIALPORT_API SERIAL_RETURN SendSerialData(long lHandle, unsigned char* pData, int nLen);接下來,我們針對性的重點介紹幾個接口的實現,內部的實現類為CSerialPortInst,以下為封裝類實現
#pragma once #include "SerialPort.h" #include "Buffer.h" using namespace SerialPort;class CSerialPortInst: public boost::enable_shared_from_this<CSerialPortInst> {typedef boost::shared_ptr<serial_port> SerialPortPtr;typedef boost::shared_ptr<boost::asio::io_service::work> IO_Work; public:CSerialPortInst();virtual ~CSerialPortInst(void);public:void SetDataCallBack(ReadCallBack pCallBack, void* pContext);void SetConnectCallBack(DisConnectCallBack pCallBack, void* pContext);long GetHandle();public:SERIAL_RETURN Open(SERIAL_PORT port, BAUT_RATE baud_rate, PARITY_TYPE parity, STOP_BIT_TYPE stop_bits, DATA_BIT_TYPE size);SERIAL_RETURN Send(unsigned char* pData, int nLen);void Close();protected:bool AsynRead();void ReadHandler(const boost::system::error_code err, const size_t nTransferedSize);void AsyncSend();void WriteHandler(CBuffer* pBuffer, const boost::system::error_code err, const size_t nTransferedSize);std::string GetPortText(SERIAL_PORT port);protected:ReadCallBack m_pDataCB; // 數據回調void* m_pDataCBUser; // 數據上下文DisConnectCallBack m_pDisconectCB; // 斷開回調void* m_pDisconectCBUser; // 斷開上下文public:boost::thread m_thread; // 服務線程IO_Work m_work; // 保活io_service m_ios; // IO 服務SerialPortPtr m_pPort; // 端口對象CBuffer m_read_buffer; // 讀緩存CSafeBuffer m_write_buffer; // 寫緩存boost::mutex m_buffer_lock; // 發送標志bool m_send_finish;protected:SERIAL_PORT m_port; // COM口BAUT_RATE m_baud_rate; // 波特率PARITY_TYPE m_parity; // 奇偶校驗 1 odd 0 even -1 noneSTOP_BIT_TYPE m_stop_bits; // 停止位DATA_BIT_TYPE m_size; // 數據位 };typedef boost::shared_ptr<CSerialPortInst> SerialPortPtr;首先,我們看打開串口核心實現:
try{boost::system::error_code ec;m_pPort->open(GetPortText(port), ec);if(0 != ec){return SERIAL_INIPORT_ERR;}// 設置波特率m_pPort->set_option(serial_port::baud_rate((unsigned int)baud_rate), ec);// 流量控制m_pPort->set_option(serial_port::flow_control(serial_port::flow_control::none), ec);// 奇偶校驗serial_port::parity::type etype = serial_port::parity::none;if(PARITY_TYPE_ODD == parity)etype = serial_port::parity::odd;else if(PARITY_TYPE_EVEN == parity)etype = serial_port::parity::even;m_pPort->set_option(serial_port::parity(etype), ec);// 停止位m_pPort->set_option( serial_port::stop_bits((serial_port::stop_bits::type)stop_bits), ec); // 數據位m_pPort->set_option( serial_port::character_size( (unsigned int)size ) ); m_port = port;m_baud_rate = baud_rate;m_parity = parity;m_stop_bits = stop_bits;m_size = size;m_work.reset(new boost::asio::io_service::work(m_ios));m_thread = boost::thread(boost::BOOST_BIND(&boost::asio::io_service::run, &m_ios));// 讀取數據AsynRead();return SERIAL_SUCCESS;}catch (...){}我們設置了串口相關參數,并啟動了一個線程不斷處理io_service的事件,因為io_service提供了任務隊列和任務分發功能且serial_port綁定到io_service中,所以當有讀取事件的時候,我們能在回掉中讀取到。
以下為信息讀取處理函數
void CSerialPortInst::ReadHandler(const boost::system::error_code err, const size_t nTransferedSize) {try{ if (!err){if (NULL != m_pDataCB && nTransferedSize > 0){m_pDataCB(GetHandle(), m_read_buffer.m_pData, nTransferedSize, m_pDataCBUser);AsynRead();}}else{if (NULL != m_pDisconectCB){m_pDisconectCB(GetHandle(), 0, m_pDisconectCBUser);}}}catch (std::exception& ){if (NULL != m_pDisconectCB){m_pDisconectCB(GetHandle(), 0, m_pDisconectCBUser);}} }很簡單,當讀取到數據的時候,我直接將數據回調到外部,讓外部程序處理該數據即可。
boost::mutex::scoped_lock a_lock(m_buffer_lock);if (m_send_finish){// 獲取當前發送bufferCBuffer* pBuffer = m_write_buffer.GetFullBuffer();// 無可發送的bufferif (NULL == pBuffer)return;if (pBuffer->m_nDataSize > 0){m_send_finish = false;unsigned int nSendLen = INT_MAX_SEND_PAKAGE_TCP;if (nSendLen > pBuffer->m_nDataSize){nSendLen = pBuffer->m_nDataSize;}m_pPort->async_write_some(buffer(pBuffer->m_pData, nSendLen),bind(&CSerialPortInst::WriteHandler, shared_from_this(), pBuffer,boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));}}除了讀取數據之外,重點就是數據的發送,同網絡庫一樣,我們使用了一個環形緩沖區,將數據直接寫入緩沖區然后由他asio庫發送,每次我們最多發送一段數據,當一段數據發送完成后我們繼續發送知道該緩沖區數據發送完成后在回收到環形緩沖區中
if (pBuffer){pBuffer->PopData(nTransferedSize);}// 數據發送完成則回收到環形緩沖區if (pBuffer->m_nDataSize <= 0){// 標志當前幀發送結束{boost::mutex::scoped_lock a_lock(m_buffer_lock);m_send_finish = true;}// 將發送完的buffer返回隊列中m_write_buffer.AddEmptyBuffer(pBuffer);// 準備發送下一幀數據AsyncSend();}else{// 發送剩余數據unsigned int nSendLen = INT_MAX_SEND_PAKAGE_TCP;if (nSendLen > pBuffer->m_nDataSize){nSendLen = pBuffer->m_nDataSize;}m_pPort->async_write_some(buffer(pBuffer->m_pData, nSendLen),bind(&CSerialPortInst::WriteHandler, shared_from_this(), pBuffer,boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));}關于關閉串口實現就更簡單了,只需要關閉serial_port并停止io_service即可:
m_pPort->close();m_work.reset();m_ios.stop();m_thread.join();三、測試
關于串口的測試就稍微有點麻煩,因為一般的機器現在很少有串口了,為了測試,我們可以使用串口模擬輔助工具:
- Configure Virtual Serial Port Driver
虛擬串口模擬工具,可以通過該軟件在本機添加一個串口對,然后你的程序就可以模擬連接該COM口了 - amcktszs
串口測試工具,為了查看發送的數據,我們可以從另一個串口中讀取數據,在測試之前,我們先看看我的庫的調用流程
具體demon的使用代碼, 連接串口(注意我默認是打開COM5口的):
BOOL CSerialDemonDlg::OnInitDialog() {CDialog::OnInitDialog();if (OpenSerialPort(SERIAL_COM5, BAUT_9600, PARITY_TYPE_NONE, STOP_BIT_1, DATA_BIT_8, &m_lHandle) != SERIAL_SUCCESS){AfxMessageBox(_T("打開COM5串口失敗!"));}else{SetDataCallBack(m_lHandle, DataCallBack, this);SetDisconnectCallBack(m_lHandle, DisConnectCB, this);}return TRUE; }發送數據:
void CSerialDemonDlg::OnBnClickedOk() {if (m_lHandle > 0){UpdateData(TRUE);SendSerialData(m_lHandle, (unsigned char *)(LPTSTR)(LPCTSTR)m_strText, m_strText.GetLength());} }接收數據處理(這里僅僅將數據打印到控制臺):
void DataCallBack(long lHandle,unsigned char* pData, int nLen, void* pContext) {unsigned char szData[1024] = {0};memcpy(szData, pData, nLen);TRACE("%d:%s\n", nLen, szData); }void DisConnectCB(long lHandle, long lType, void* pContext) {TRACE("鏈接斷開\n"); }關閉串口:
void CSerialDemonDlg::OnClose() {if (m_lHandle > 0){CloseSerialPort(m_lHandle);m_lHandle = -1;}CDialog::OnClose(); }我的demon啟動界面如下:
首先,我們使用窗口工具創建一個串口對,我這里使用的串口是COM5,所以我建立了一個串口對COM5和COM6
添加完成后可以看到機器上多了兩個虛擬串口COM5和COM6,那么接下來我們就可以連接這兩個串口來發送和讀取數據了,首先我們測試數據發送。
數據發送測試
在COM5上發送數據,在COM6上接收數據, 首先,我們使用第三方的調試工具amcktszs在COM6上讀取數據
此時右側是沒有任何數據的,我們啟動我的demon,然后發送數據12345,查看測試工具上右側讀取的數據
可以看到測試工具已經讀取到了我發送的123456字符串,它是以16進制顯示的,正好對應為"31 32 33 34 35"
數據讀取測試
接下來,我們在COM6上發送數據–測試工具,然后在COM5上讀取數據-我的demon,看看demon控制臺打印:
以上就是使用boost庫實現了一個跨平臺的串口通信庫的開發,該庫可以支持多個com口同時通信,如果需要該庫的源碼請聯系個人付費獲取,如果需要技術交流,請進入群聊。
源碼獲取、合作、技術交流請獲取如下聯系方式:
QQ交流群:961179337
微信賬號:lixiang6153
公眾號:IT技術快餐
電子郵箱:lixx2048@163.com
總結
以上是生活随笔為你收集整理的boost网络串口通信库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 编译ffmpeg库
- 下一篇: 22年低分上岸成都电子科技大学计算机经验