在嵌入式开发和工业控制领域,串口通信是最基础也最常用的通信方式之一。但让很多开发者头疼的是,不同操作系统下的串口实现差异巨大。Windows用COM端口和Win32 API,Linux依赖termios和/dev/tty设备,macOS又是另一套IOKit机制。我曾经接手过一个需要同时在Windows工控机和Linux网关运行的串口项目,光是处理平台差异就浪费了两周时间。
CSerialPort这个开源库的出现完美解决了这个问题。它用C++封装了各平台的底层实现,提供统一的API接口。最新4.3.x版本更是通过CMake实现了真正的跨平台构建,开发者再也不用为不同平台维护多套工程文件。我实测在三个平台编译同一份代码,从克隆仓库到生成可执行文件,整个过程不超过5分钟。
早期版本集成CSerialPort需要手动拷贝源码到项目目录,这种"暴力include"的方式存在明显缺陷:一是难以更新库版本,二是污染项目结构。我在2018年的一个项目中就踩过坑 - 当需要升级CSerialPort修复某个串口阻塞bug时,发现散落在多个项目中的副本已经出现分化,最后不得不重写所有串口相关代码。
现代CMake提供了更优雅的依赖管理方案,这里我实测对比三种主流方法:
add_subdirectory
适合需要修改库源码的场景,直接将CSerialPort作为子模块添加到主项目:
bash复制git submodule add https://github.com/itas109/CSerialPort
CMakeLists.txt配置示例:
cmake复制add_subdirectory(CSerialPort)
target_link_libraries(MyApp PRIVATE CSerialPort)
FetchContent
CMake 3.11+推荐的方式,编译时自动下载源码,适合作为纯依赖使用:
cmake复制include(FetchContent)
FetchContent_Declare(
CSerialPort
GIT_REPOSITORY https://github.com/itas109/CSerialPort
GIT_TAG v4.3.0
)
FetchContent_MakeAvailable(CSerialPort)
find_package
需要预先安装CSerialPort到系统目录,适合团队协作环境:
cmake复制find_package(CSerialPort REQUIRED)
target_link_libraries(MyApp PRIVATE CSerialPort::CSerialPort)
实测下来,小型项目推荐FetchContent,中大型项目建议用add_subdirectory结合git submodule。
我们先创建一个最简跨平台串口终端项目:
code复制SerialTerminal/
├── CMakeLists.txt
├── src/
│ └── main.cpp
└── extern/
└── CSerialPort (submodule)
关键CMake配置要点:
cmake复制cmake_minimum_required(VERSION 3.12)
project(SerialTerminal LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加子模块
add_subdirectory(extern/CSerialPort)
# 可执行文件配置
add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE
CSerialPort
)
# 平台特定依赖
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE setupapi)
elseif(UNIX AND NOT APPLE)
target_link_libraries(${PROJECT_NAME} PRIVATE pthread)
endif()
不同平台需要特别注意这些配置项:
Windows平台:
cpp复制#ifdef _WIN32
sp.init("COM3", BaudRate115200, ParityNone, DataBits8, StopOne);
#else
sp.init("/dev/ttyUSB0", BaudRate115200, ParityNone, DataBits8, StopOne);
#endif
Linux/macOS平台:
cmake复制if(APPLE)
find_library(IOKIT IOKit)
find_library(FOUNDATION Foundation)
target_link_libraries(${PROJECT_NAME} PRIVATE ${IOKIT} ${FOUNDATION})
endif()
问题1:找不到serial_port.h头文件
解决方案:确保CMake正确设置了包含路径:
cmake复制target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/extern/CSerialPort/include
)
问题2:Linux下链接失败报未定义引用
检查是否链接了pthread库,现代CMake推荐这样写:
cmake复制find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)
问题1:Windows下返回错误代码121
这是典型的权限问题,三种解决方法:
问题2:Linux下提示设备忙
通常是因为上次使用后没有正确关闭端口,执行命令释放:
bash复制sudo lsof /dev/ttyUSB0
sudo kill -9 <占用进程PID>
推荐在CI/CD流程中加入跨平台编译检查,这是我在GitLab CI中使用的配置片段:
yaml复制build:linux:
image: ubuntu:20.04
script:
- apt-get update && apt-get install -y g++ cmake
- mkdir build && cd build
- cmake .. -DCMAKE_BUILD_TYPE=Release
- make -j4
build:windows:
image: mcr.microsoft.com/windows:20H2
script:
- choco install cmake -y
- mkdir build
- cd build
- cmake .. -G "Visual Studio 16 2019"
- cmake --build . --config Release
cpp复制sp.init(..., 4096); // 设置读缓冲区大小
cpp复制sp.setReadIntervalTimeout(50); // 单位毫秒
cpp复制sp.setDebugModel(true);
在最近的一个工业物联网项目中,通过调整这些参数,我们成功将串口通信的稳定性从97%提升到99.9%。特别是在Linux环境下,合理的超时设置能有效避免线程阻塞问题。