一、前言

前面我写了一篇《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
    串口测试工具,为了查看发送的数据,我们可以从另一个串口中读取数据,在测试之前,我们先看看我的库的调用流程
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

boost网络串口通信库相关推荐

  1. PySerial:Python串口通信库的详细介绍、安装及使用方法攻略

    PySerial:Python串口通信库的详细介绍.安装及使用方法攻略 一.PySerial 简介 PySerial 是 Python 的一个串口通信库,支持不同平台下的串口操作.在 Python 应 ...

  2. AndroidSerialPort:安卓串口通信库

    AndroidSerialPort Android 串口通信,基于谷歌官方android-serialport-api编译 github地址:github.com/AIlll/Andro- 使用说明 ...

  3. python第三方库之学习pyserial库--串口通信

    pyserial串口通信库 1.安装pyserial库 2.填写串口参数的注意事项 3.简单封装一下 4.碰到的bug 1.安装pyserial库 pip install pyserial versi ...

  4. Android网络请求通信之Volley

    一.Volley简介 Volley网络框架是Google公司在2013年发布的一款Android平台上的网络请求通信库.以下是对Volley的简单归纳. Volley的优点: 使网络通信更快.更简单. ...

  5. python中串口通信的步骤及实现

    python内置的库函数确实很强大,serial库中包含了串口通信所用到的一些函数.本文用python实现了串口的一种简单通信. 代码实现: import serial#导入串口通信库 from ti ...

  6. 谷歌Android开源串口通信使用

    Demo下载地址: 谷歌官方串口库使用 引言: 现在的串口通信多用于嵌入设备中,Android主板与各种板卡之间的通信.因此串口通信在未来智能设备中应用会很广泛. 现在市面上几乎所有的Android串 ...

  7. Windows下使用Python实现串口通信

    Windows下使用Python实现串口通信 基本信息 配置过程 配置思路 详细配置过程 安装USB装TTL驱动 配置serial库 获取设备端口号 配置串口通信 配置说明 测试过程 其他学习记录 参 ...

  8. Java串口通信-JSerialComm

    Java串口通信-JSerialComm 目前网上的Java串口通信主要使用RXTXComm,但是这个库已经很久没有更新(最近的更新似乎在2012年),并且与JavaFX集成打包时会出现BUG.JSe ...

  9. boost网络库开发

    一.前言 网络库是从事C++开发最基础.最核心.最常用的一个库,所有的协议都是建立在一个稳定高效的网络库之上的,所以对于c++程序员来说它是必不可少且非常非常重要的一个核心组件,我们可以使用网络库做任 ...

最新文章

  1. 平均 15189 元!2021 年 3 月程序员工资统计出炉
  2. c#一种存储结构解决动态平衡问题
  3. php中调行高代码_单元格行高怎么设置
  4. python编码注释和平台注释_python注释是什么意思
  5. CodeForces - 892E Envy(可撤销并查集)
  6. Tkinter Helloword !
  7. 【Kafka】Kafka 配置 SCRAM认证
  8. 如何检测元素外部的点击?
  9. ubuntu 虚拟机(转)
  10. 用 ASP.NET 管理 IIS(转)
  11. 用matlab编模糊pid程序,实例:MATLAB/Simulink实现模糊PID控制
  12. ARCGIS基础到INVEST建模
  13. 实时数仓在有赞的实践
  14. Activity透明主题导致behind Activity重绘的解决方法
  15. 利用反射动态修改 EasyPoi 导出Excel表格标题名称
  16. 前端学习日记3-如何设置网页背景图片
  17. MySQL8.0.17 - Multi-Valued Indexes 简述
  18. Django——admin功能、注册模型类、模型管理类
  19. Shipping Grants
  20. 多传感器融合定位十五-多传感器时空标定(综述)

热门文章

  1. 鸿蒙开发套件全面升级,助力鸿蒙生态蓬勃发展
  2. 2021-10-10 10:10:10
  3. 高仿微信APP实战(一)-Actionbar的制作与应用
  4. Hibernate使用原生SQL语句(left join左连接查询)
  5. PHP高精度计算函数
  6. 新手Linux命令-2
  7. 员工一言不合就离职怎么办?我用 Python 写了个员工流失预测模型
  8. xmind思维导图文件如何不下载任何软件进行查看
  9. 对一阶电路的瞬态分析
  10. U盘提示损坏或者无法读取 / 修复