CSerialPort笔记
前面的博客讲了如何以第三库方式使用CSerialPort,下面将介绍如何通过cmake直接编译与使用CSerialPort。
我是比较推荐使用CSerialPort的,在大二的时候做过的多轴控制软件(基于VC++6.0 MFC)便是使用itas109作者写的串口助手改写的,串口功能流程稳定,API方便易懂。
前言
CSerialPort项目是基于C++的轻量级开源跨平台串口类库,用于实现跨平台多操作系统的串口读写。
CSerialPort项目的开源协议自 V3.0.0.171216
版本后采用GNU Lesser General Public License v3.0
CSerialPort项目地址:
CSerialPort设计原则
- 跨平台
- 简单易用
- 高效
CSerialPort平台支持
CSerialPort已经在以下平台做过测试:
- DOS ( x86_64 )
- Windows ( x86_64 )
- Linux ( x86_64, aarch64, mips64el, s390x, ppc64le )
- macOS ( x86_64 )
- Raspberry Pi ( armv7l )
- FreeBSD ( x86_64 )
- …
cmake构建基于CSerialPort的项目
1 建立项目文件夹
新建一个文件夹,内容如下
CMakeLists.txt
main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)
project(UartTest LANGUAGES CXX)
# add by itas109
set(CSerialPortRootPath "${PROJECT_SOURCE_DIR}/CSerialPort")
include_directories(${CSerialPortRootPath}/include)
list(APPEND CSerialPortSourceFiles ${CSerialPortRootPath}/src/SerialPort.cpp ${CSerialPortRootPath}/src/SerialPortBase.cpp ${CSerialPortRootPath}/src/SerialPortInfo.cpp ${CSerialPortRootPath}/src/SerialPortInfoBase.cpp)
if (WIN32)
list(APPEND CSerialPortSourceFiles ${CSerialPortRootPath}/src/SerialPortInfoWinBase.cpp ${CSerialPortRootPath}/src/SerialPortWinBase.cpp)
else (UNIX)
list(APPEND CSerialPortSourceFiles ${CSerialPortRootPath}/src/SerialPortInfoUnixBase.cpp ${CSerialPortRootPath}/src/SerialPortUnixBase.cpp)
endif()
# end by itas109
add_executable(${PROJECT_NAME}
main.cpp
${CSerialPortSourceFiles} # add by itas109
)
# add by itas109
if (WIN32)
# for function availableFriendlyPorts
target_link_libraries( ${PROJECT_NAME} setupapi)
elseif(APPLE)
find_library(IOKIT_LIBRARY IOKit)
find_library(FOUNDATION_LIBRARY Foundation)
target_link_libraries( ${PROJECT_NAME} ${FOUNDATION_LIBRARY} ${IOKIT_LIBRARY})
elseif(UNIX)
target_link_libraries( ${PROJECT_NAME} pthread)
endif ()
# end by itas109
命令解释
这段代码是一个CMakeLists.txt文件,用于配置CMake构建系统。以下逐行解释每个命令的含义:
cmake_minimum_required(VERSION 2.8.12)
:这个命令指定了最低的CMake版本要求。
project(UartTest LANGUAGES CXX)
:这个命令定义了项目的名称为"UartTest",并指定了项目使用的编程语言为C++(LANGUAGES CXX)。
set(CSerialPortRootPath "${PROJECT_SOURCE_DIR}/CSerialPort")
:这个命令设置了一个变量CSerialPortRootPath
,它表示"CSerialPort"项目的根目录路径,该路径被设置为当前项目的源代码目录下的"CSerialPort"文件夹。
include_directories(${CSerialPortRootPath}/include)
:这个命令将"CSerialPort"项目的include目录添加到编译器的头文件搜索路径中,以便在构建期间能够找到相关的头文件。
list(APPEND CSerialPortSourceFiles ...)
:这个命令将一系列源文件路径添加到变量CSerialPortSourceFiles
中。这些源文件是"CSerialPort"项目的源代码文件。
if (WIN32) ... else (UNIX) ... endif()
:这个条件语句用于根据操作系统的不同选择不同的源文件。如果操作系统是Windows(WIN32),则添加Windows特定的源文件;否则(UNIX),添加Unix特定的源文件。
add_executable(${PROJECT_NAME} ...)
:这个命令将一个可执行文件的名称设置为${PROJECT_NAME}
,并指定了可执行文件的源代码文件。${PROJECT_NAME}
是之前通过project()
命令定义的项目名称。
target_link_libraries(...)
:这个命令用于指定可执行文件的链接库。根据操作系统的不同,选择不同的链接库。在Windows上,使用setupapi
库;在苹果操作系统上,使用IOKit
和Foundation
库;在Unix系统上,使用pthread
库。通过执行这些命令,CMake将配置项目的构建过程,设置了编译器的头文件搜索路径,添加了项目的源代码文件,并指定了可执行文件的链接库。
main.cpp
#include <iostream>
#ifdef _WIN32
#include <Windows.h>
#define imsleep(microsecond) Sleep(microsecond) // ms
#else
#include <unistd.h>
#define imsleep(microsecond) usleep(1000 * microsecond) // ms
#endif
#include <vector>
#include "CSerialPort/SerialPort.h"
#include "CSerialPort/SerialPortInfo.h"
using namespace itas109;
std::string char2hexstr(const char *str, int len)
{
static const char hexTable[17] = "0123456789ABCDEF";
std::string result;
for (int i = 0; i < len; ++i)
{
result += "0x";
result += hexTable[(unsigned char)str[i] / 16];
result += hexTable[(unsigned char)str[i] % 16];
result += " ";
}
return result;
}
int countRead = 0;
class MyListener : public CSerialPortListener
{
public:
MyListener(CSerialPort *sp)
: p_sp(sp){};
void onReadEvent(const char *portName, unsigned int readBufferLen)
{
if (readBufferLen > 0)
{
char *data = new char[readBufferLen + 1]; // '\0'
if (data)
{
// read
int recLen = p_sp->readData(data, readBufferLen);
if (recLen > 0)
{
data[recLen] = '\0';
std::cout << portName << " - Count: " << ++countRead << ", Length: " << recLen << ", Str: " << data << ", Hex: " << char2hexstr(data, recLen).c_str()
<< std::endl;
// return receive data
// p_sp->writeData(data, recLen);
}
delete[] data;
data = NULL;
}
}
};
private:
CSerialPort *p_sp;
};
int main()
{
CSerialPort sp;
std::cout << "Version: " << sp.getVersion() << std::endl
<< std::endl;
MyListener listener(&sp);
std::vector<SerialPortInfo> m_availablePortsList = CSerialPortInfo::availablePortInfos();
std::cout << "availableFriendlyPorts: " << std::endl;
for (size_t i = 1; i <= m_availablePortsList.size(); ++i)
{
SerialPortInfo serialPortInfo = m_availablePortsList[i - 1];
std::cout << i << " - " << serialPortInfo.portName << " " << serialPortInfo.description << " " << serialPortInfo.hardwareId << std::endl;
}
// 检查是否有可用串口,如果没有则退出
if (m_availablePortsList.size() == 0)
{
std::cout << "No valid port" << std::endl;
return 0;
}
std::cout << std::endl;
const char *portName = "COM10";
std::cout << "Port Name: " << portName << std::endl;
sp.init(portName, // windows:COM1 Linux:/dev/ttyS0
itas109::BaudRate115200, // baudrate
itas109::ParityNone, // parity
itas109::DataBits8, // data bit
itas109::StopOne, // stop bit
itas109::FlowNone, // flow
4096 // read buffer size
);
sp.setReadIntervalTimeout(0); // read interval timeout 0ms
sp.open();
std::cout << "Open " << portName << (sp.isOpen() ? " Success" : " Failed") << std::endl;
// 连接读取事件
sp.connectReadEvent(&listener);
// 第一次写入十六进制数据
char hex[5];
hex[0] = 0x31;
hex[1] = 0x32;
hex[2] = 0x33;
hex[3] = 0x34;
hex[4] = 0x35;
sp.writeData(hex, sizeof(hex));
// 写入字符串数据
sp.writeData("Dapenson", 8);
// 循环等待
for (;;)
{
// 延时1S
imsleep(1000);
// 写入字符串数据
const char *data_send = "Dapenson\n";
sp.writeData(data_send, strlen(data_send));
}
return 0;
}
程序功能思路解释
这段C++程序的功能是通过串口COM10与外部设备进行通信。下面是程序的主要功能和思路的简要解释:
程序包含了必要的头文件和库,并定义了一些辅助函数和变量。
声明了一个名为
MyListener
的自定义类,该类继承自CSerialPortListener
,用于处理串口的读取事件。在
main
函数中,首先创建了一个CSerialPort
对象sp
,并打印出其版本信息。创建了一个
MyListener
对象listener
,并使用CSerialPort
对象的availablePortInfos
方法获取可用串口的列表,并打印出相应的信息。检查是否有可用串口,如果没有则退出程序。
使用指定的串口名称、波特率、校验位等参数对
CSerialPort
对象进行初始化,并设置读取超时时间。打开串口,并输出打开状态。
连接读取事件,将
MyListener
对象的读取事件与CSerialPort
对象进行关联。写入一些数据到串口,包括一组十六进制数据和一个字符串。
进入无限循环,每隔1秒发送一个字符串数据到串口。
整体思路是:程序首先初始化
CSerialPort
对象,然后打开指定的串口。接着,将一个自定义的监听器对象与串口的读取事件进行关联,以便在有数据可读时触发相应的处理。程序在主循环中以一定的时间间隔不断地向串口发送数据。通过这样的方式,可以实现与外部设备的串口通信,并在控制台输出接收到的数据。
2 cmake构建
在项目文件夹下打开cmd,并使用以下命令进行构建
git clone https://github.com/itas109/CSerialPort
# 或使用国内码云地址
git clone https://gitee.com/itas109/CSerialPort.git
mkdir bin
cd bin
cmake .. -G "MinGW Makefiles"
make
或
cmake --build .
命令解释:
这些是一系列Bash命令,用于克隆GitHub或码云上的一个名为"CSerialPort"的项目,并在本地进行构建。让我一步步解释每个命令的含义:
git clone https://github.com/itas109/CSerialPort
:这个命令会克隆GitHub上的"CSerialPort"项目到当前目录。git clone
用于复制一个远程Git仓库到本地。或者,如果你想使用码云上的地址,可以执行以下命令:
git clone https://gitee.com/itas109/CSerialPort.git
:这个命令会克隆码云上的"CSerialPort"项目到当前目录。
mkdir bin
:这个命令创建一个名为"bin"的目录。mkdir
用于创建新目录。
cd bin
:这个命令将当前工作目录切换到"bin"目录。cd
用于改变当前工作目录。
cmake .. -G "MinGW Makefiles"
:这个命令使用CMake来配置项目的构建过程。cmake
是一个跨平台的构建工具,它根据项目中的CMakeLists.txt文件生成适合特定构建系统(在这里是MinGW Makefiles)的构建文件。
make
:这个命令用于执行构建过程,编译源代码并生成可执行文件。这是针对类Unix系统的命令。如果你正在Windows上使用MinGW,可以使用mingw32-make
代替make
。或者,如果你已经使用了
-G "MinGW Makefiles"
选项进行了配置,也可以使用以下命令进行构建:
cmake --build .
:这个命令使用预先配置的构建系统构建项目。通过执行这些命令,你将克隆"CSerialPort"项目,创建一个名为"bin"的目录,配置构建过程,并使用Makefile(或MinGW Makefiles)构建项目。
打印信息
运行完成以后会在bin目录下生成一个以项目名称为名的exe可执行文件
可双击运行打开cmd窗口
以下是项目编译运行的最简环境
+--- CMakeLists.txt +--- main.cpp +--- CSerialPort | +--- include | | +--- CSerialPort | | | +--- SerialPort.h | | | +--- SerialPortInfo.h | +--- src | | +--- SerialPort.cpp | | +--- SerialPortBase.cpp | | +--- SerialPortInfo.cpp | | +--- SerialPortInfoBase.cpp | | +--- SerialPortInfoWinBase.cpp | | +--- SerialPortWinBase.cpp
3 测试效果
程序功能是打开COM10,并向其每隔1s发送一次固定字符串,同时绑定的回调函数将COM10收到数据打印。
因此使用VSPD建立一组串口(COM10、COM11)用以测试,使用串口助手软件打开COM11用以与程序联调测试。
下面是正常运行的效果图,左边将右边发送的1234
字符串打印
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义