前面我写了一篇《boost开发网络库》一文,该文章介绍了使用boost库开发一个高效、稳定的网络通讯库,其中用到了c++准标准库boost的asio网络通讯模块,本文将要讲的是使用boost开发usb串口,正好也用到了asio,我以前文章中说过asio不只仅包含网络通讯,还包括串口,接下来我将带你们讲解使用boost库实现串口的通讯。(固然,咱们彻底可使用windows本地api实现相似功能) linux
串口是一种很是通用的设备通讯的协议(不要与通用串行总线Universal Serial Bus(USB)混淆)。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通讯协议;不少GPIB兼容的设备也带有RS-232口。同时,串口通讯协议也能够用于获取远程采集设备的数据。 ios
串口通讯的概念很是简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通讯慢,可是串口能够在使用一根线发送数据的同时用另外一根线接收数据。它很简单而且可以实现远距离通讯。好比IEEE488定义并行通行状态时,规定设备线总长不得超过20米,而且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。
典型地,串口用于ASCII码字符的传输。通讯使用3根线完成:(1)地线,(2)发送,(3)接收。因为串口通讯是异步的,端口可以在一根线上发送数据同时在另外一根线上接收数据。其余线用于握手,但不是必须的。串口通讯最重要的参数是波特率、数据位、中止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配。c++
首先,咱们要了解串口通讯的几个概念:windows
这是一个衡量符号传输速率的参数。它表示每秒钟传送的符号的个数。例如300波特表示每秒钟发送300个符号。当咱们提到时钟周期时,咱们就是指波特率,例如若是协议须要4800波特率,那么时钟是4800Hz。这意味着串口通讯在数据线上的采样率为4800Hz。一般电话线的波特率为14400,28800和36600。波特率能够远远大于这些值,可是波特率和距离成反比。高波特率经常用于放置的很近的仪器间的通讯,典型的例子就是GPIB设备的通讯。api
这是衡量通讯中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是五、六、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下。 async
首先,个人目的很简单,就是实现串口链接、关闭、发送数据、接收数据,因此个人接口也是这几个功能:
// ------------------------------------------------------------------------------ // 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 none STOP_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) { // 获取当前发送buffer CBuffer* pBuffer = m_write_buffer.GetFullBuffer(); // 无可发送的buffer if (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();
关于串口的测试就稍微有点麻烦,由于通常的机器如今不多有串口了,为了测试,咱们可使用串口模拟辅助工具:
虚拟串口模拟工具,能够经过该软件在本机添加一个串口对,而后你的程序就能够模拟链接该COM口了
串口测试工具,为了查看发送的数据,咱们能够从另外一个串口中读取数据,在测试以前,咱们先看看个人库的调用流程
OpenSerialPort打开串口=>SetDataCallBack设置数据回调=>SetDisconnectCallBack设置链接断开回调=>SendSerialData发送数据给串口=>CloseSerialPort关闭串口
具体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