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

image-20230521170513495

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构建系统。以下逐行解释每个命令的含义:

  1. cmake_minimum_required(VERSION 2.8.12):这个命令指定了最低的CMake版本要求。

  2. project(UartTest LANGUAGES CXX):这个命令定义了项目的名称为"UartTest",并指定了项目使用的编程语言为C++(LANGUAGES CXX)。

  3. set(CSerialPortRootPath "${PROJECT_SOURCE_DIR}/CSerialPort"):这个命令设置了一个变量CSerialPortRootPath,它表示"CSerialPort"项目的根目录路径,该路径被设置为当前项目的源代码目录下的"CSerialPort"文件夹。

  4. include_directories(${CSerialPortRootPath}/include):这个命令将"CSerialPort"项目的include目录添加到编译器的头文件搜索路径中,以便在构建期间能够找到相关的头文件。

  5. list(APPEND CSerialPortSourceFiles ...):这个命令将一系列源文件路径添加到变量CSerialPortSourceFiles中。这些源文件是"CSerialPort"项目的源代码文件。

  6. if (WIN32) ... else (UNIX) ... endif():这个条件语句用于根据操作系统的不同选择不同的源文件。如果操作系统是Windows(WIN32),则添加Windows特定的源文件;否则(UNIX),添加Unix特定的源文件。

  7. add_executable(${PROJECT_NAME} ...):这个命令将一个可执行文件的名称设置为${PROJECT_NAME},并指定了可执行文件的源代码文件。${PROJECT_NAME}是之前通过project()命令定义的项目名称。

  8. target_link_libraries(...):这个命令用于指定可执行文件的链接库。根据操作系统的不同,选择不同的链接库。在Windows上,使用setupapi库;在苹果操作系统上,使用IOKitFoundation库;在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与外部设备进行通信。下面是程序的主要功能和思路的简要解释:

  1. 程序包含了必要的头文件和库,并定义了一些辅助函数和变量。

  2. 声明了一个名为MyListener的自定义类,该类继承自CSerialPortListener,用于处理串口的读取事件。

  3. main函数中,首先创建了一个CSerialPort对象sp,并打印出其版本信息。

  4. 创建了一个MyListener对象listener,并使用CSerialPort对象的availablePortInfos方法获取可用串口的列表,并打印出相应的信息。

  5. 检查是否有可用串口,如果没有则退出程序。

  6. 使用指定的串口名称、波特率、校验位等参数对CSerialPort对象进行初始化,并设置读取超时时间。

  7. 打开串口,并输出打开状态。

  8. 连接读取事件,将MyListener对象的读取事件与CSerialPort对象进行关联。

  9. 写入一些数据到串口,包括一组十六进制数据和一个字符串。

  10. 进入无限循环,每隔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"的项目,并在本地进行构建。让我一步步解释每个命令的含义:

  1. git clone https://github.com/itas109/CSerialPort:这个命令会克隆GitHub上的"CSerialPort"项目到当前目录。git clone用于复制一个远程Git仓库到本地。

或者,如果你想使用码云上的地址,可以执行以下命令:

git clone https://gitee.com/itas109/CSerialPort.git:这个命令会克隆码云上的"CSerialPort"项目到当前目录。

  1. mkdir bin:这个命令创建一个名为"bin"的目录。mkdir用于创建新目录。

  2. cd bin:这个命令将当前工作目录切换到"bin"目录。cd用于改变当前工作目录。

  3. cmake .. -G "MinGW Makefiles":这个命令使用CMake来配置项目的构建过程。cmake是一个跨平台的构建工具,它根据项目中的CMakeLists.txt文件生成适合特定构建系统(在这里是MinGW Makefiles)的构建文件。

  4. make:这个命令用于执行构建过程,编译源代码并生成可执行文件。这是针对类Unix系统的命令。如果你正在Windows上使用MinGW,可以使用mingw32-make代替make

或者,如果你已经使用了-G "MinGW Makefiles"选项进行了配置,也可以使用以下命令进行构建:

cmake --build .:这个命令使用预先配置的构建系统构建项目。

通过执行这些命令,你将克隆"CSerialPort"项目,创建一个名为"bin"的目录,配置构建过程,并使用Makefile(或MinGW Makefiles)构建项目。

打印信息

image-20230521171552624

运行完成以后会在bin目录下生成一个以项目名称为名的exe可执行文件

image-20230521171653962

可双击运行打开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字符串打印

image-20230521171230361

posted @ 2023-05-21 17:35  Dapenson  阅读(543)  评论(0编辑  收藏  举报