前面的博客讲了如何以第三库方式使用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项目地址:
https://github.com/itas109/CSerialPort
https://gitee.com/itas109/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
#ifdef _WIN32
#include
#define imsleep(microsecond) Sleep(microsecond) // ms
#else
#include
#define imsleep(microsecond) usleep(1000 * microsecond) // ms
#endif
#include
#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
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字符串打印