You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Ubuntu系统下使用C++获取USB设备序列号的方法

如何读取USB串口设备的序列号?

我需要读取USB设备的序列号,系统日志示例如下:

[89185.825492 < 68.912895>] usb 1-4: new full-speed USB device number 41 using xhci_hcd
[89185.975679 < 0.150187>] usb 1-4: New USB device found, idVendor=03eb, idProduct=2404, bcdDevice= 1.00
[89185.975685 < 0.000006>] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[89185.975689 < 0.000004>] usb 1-4: Product: USB Roboclaw 2x30A
[89185.975692 < 0.000003>] usb 1-4: Manufacturer: Basicmicro Inc.
[89185.975695 < 0.000003>] usb 1-4: SerialNumber: 260
[89185.977910 < 0.002215>] cdc_acm 1-4:1.0: ttyACM0: USB ACM device

示例中目标序列号是260。我已经用以下代码实现了串口通信,但不清楚打开端口后如何获取这个序列号:

#include "TimeoutSerial.h"
#include <string>
#include <algorithm>
#include <iostream>
#include <boost/bind.hpp>

using namespace std;
using namespace boost;

TimeoutSerial::TimeoutSerial(): io(), port(io), timer(io),
                                timeout(boost::posix_time::seconds(0)) {}

TimeoutSerial::TimeoutSerial(const std::string& devname, unsigned int baud_rate,
                             asio::serial_port_base::parity opt_parity,
                             asio::serial_port_base::character_size opt_csize,
                             asio::serial_port_base::flow_control opt_flow,
                             asio::serial_port_base::stop_bits opt_stop)
        : io(), port(io), timer(io), timeout(boost::posix_time::seconds(0))
{
    open(devname,baud_rate,opt_parity,opt_csize,opt_flow,opt_stop);
}

void TimeoutSerial::open(const std::string& devname, unsigned int baud_rate,
                         asio::serial_port_base::parity opt_parity,
                         asio::serial_port_base::character_size opt_csize,
                         asio::serial_port_base::flow_control opt_flow,
                         asio::serial_port_base::stop_bits opt_stop)
{
    if(isOpen()) close();
    port.open(devname);
    port.set_option(asio::serial_port_base::baud_rate(baud_rate));
    port.set_option(opt_parity);
    port.set_option(opt_csize);
    port.set_option(opt_flow);
    port.set_option(opt_stop);

}

bool TimeoutSerial::isOpen() const
{
    return port.is_open();
}

void TimeoutSerial::close()
{
    if(isOpen()==false) return;
    port.close();
}

void TimeoutSerial::setTimeout(const boost::posix_time::time_duration& t)
{
    timeout=t;
}

void TimeoutSerial::write(const char *data, size_t size)
{
    asio::write(port,asio::buffer(data,size));
}

void TimeoutSerial::write(const std::vector<char>& data)
{
    asio::write(port,asio::buffer(&data[0],data.size()));
}

void TimeoutSerial::writeString(const std::string& s)
{
    asio::write(port,asio::buffer(s.c_str(),s.size()));
}

void TimeoutSerial::read(char *data, size_t size)
{
    if(readData.size()>0)//If there is some data from a previous read
    {
        istream is(&readData);
        size_t toRead=min(readData.size(),size);//How many bytes to read?
        is.read(data,toRead);
        data+=toRead;
        size-=toRead;
        if(size==0) return;//If read data was enough, just return
    }

    setupParameters=ReadSetupParameters(data,size);
    performReadSetup(setupParameters);

    //For this code to work, there should always be a timeout, so the
    //request for no timeout is translated into a very long timeout
    if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout);
    else timer.expires_from_now(boost::posix_time::hours(100000));

    timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this,
                                 asio::placeholders::error));

    result=resultInProgress;
    bytesTransferred=0;
    for(;;)
    {
        io.run_one();
        switch(result)
        {
            case resultSuccess:
                timer.cancel();
                return;
            case resultTimeoutExpired:
                port.cancel();
                throw(timeout_exception("Timeout expired"));
            case resultError:
                timer.cancel();
                port.cancel();
                throw(boost::system::system_error(boost::system::error_code(),
                                                  "Error while reading"));
                //if resultInProgress remain in the loop
        }
    }
}

std::vector<char> TimeoutSerial::read(size_t size)
{
    vector<char> result(size,'\0');//Allocate a vector with the desired size
    read(&result[0],size);//Fill it with values
    return result;
}

std::string TimeoutSerial::readString(size_t size)
{
    string result(size,'\0');//Allocate a string with the desired size
    read(&result[0],size);//Fill it with values
    return result;
}

std::string TimeoutSerial::readStringUntil(const std::string& delim)
{
    // Note: if readData contains some previously read data, the call to
    // async_read_until (which is done in performReadSetup) correctly handles
    // it. If the data is enough it will also immediately call readCompleted()
    setupParameters=ReadSetupParameters(delim);
    performReadSetup(setupParameters);

    //For this code to work, there should always be a timeout, so the
    //request for no timeout is translated into a very long timeout
    if(timeout!=boost::posix_time::seconds(0)) timer.expires_from_now(timeout);
    else timer.expires_from_now(boost::posix_time::hours(100000));

    timer.async_wait(boost::bind(&TimeoutSerial::timeoutExpired,this,
                                 asio::placeholders::error));

    result=resultInProgress;
    bytesTransferred=0;
    for(;;)
    {
        io.run_one();
        switch(result)
        {
            case resultSuccess:
            {
                timer.cancel();
                bytesTransferred-=delim.size();//Don't count delim
                istream is(&readData);
                string result(bytesTransferred,'\0');//Alloc string
                is.read(&result[0],bytesTransferred);//Fill values
                is.ignore(delim.size());//Remove delimiter from stream
                return result;
            }
            case resultTimeoutExpired:
                port.cancel();
                throw(timeout_exception("Timeout expired"));
            case resultError:
                timer.cancel();
                port.cancel();
                throw(boost::system::system_error(boost::system::error_code(),
                                                  "Error while reading"));
                //if resultInProgress remain in the loop
        }
    }
}

TimeoutSerial::~TimeoutSerial() {}

void TimeoutSerial::performReadSetup(const ReadSetupParameters& param)
{
    if(param.fixedSize)
    {
        asio::async_read(port,asio::buffer(param.data,param.size),boost::bind(
                &TimeoutSerial::readCompleted,this,asio::placeholders::error,
                asio::placeholders::bytes_transferred));
    } else {
        asio::async_read_until(port,readData,param.delim,boost::bind(
                &TimeoutSerial::readCompleted,this,asio::placeholders::error,
                asio::placeholders::bytes_transferred));
    }
}

void TimeoutSerial::timeoutExpired(const boost::system::error_code& error)
{
    if(!error && result==resultInProgress) result=resultTimeoutExpired;
}

void TimeoutSerial::readCompleted(const boost::system::error_code& error,
                                  const size_t bytesTransferred)
{
    if(!error)
    {
        result=resultSuccess;
        this->bytesTransferred=bytesTransferred;
        return;
    }

    //In case a asynchronous operation is cancelled due to a timeout,
    //each OS seems to have its way to react.
#ifdef _WIN32
    if(error.value()==995) return; //Windows spits out error 995
#elif defined(__APPLE__)
    if(error.value()==45)
    {
        //Bug on OS X, it might be necessary to repeat the setup
        performReadSetup(setupParameters);
        return;
    }
#else //Linux
    if(error.value()==125) return; //Linux outputs error 125
#endif

    result=resultError;
}

请问是否有办法获取该序列号的值?


解决方案

有两种可靠的方式获取这个USB设备的序列号,不需要依赖串口通信本身的交互:

1. 从系统sysfs文件读取(Linux环境)

你的设备是CDC-ACM类型串口(对应/dev/ttyACM0),Linux会在sysfs文件系统中维护所有USB设备的元数据,直接读取即可:

#include <fstream>
#include <string>
#include <filesystem>

std::string get_usb_serial(const std::string& tty_device) {
    // 构建sysfs路径:从tty设备回溯到USB根设备
    std::string sys_path = "/sys/class/tty/" + tty_device + "/device/../";
    std::ifstream serial_file(sys_path + "serial");
    std::string serial;
    if (serial_file.is_open()) {
        std::getline(serial_file, serial);
        serial_file.close();
    }
    return serial;
}

// 使用示例:获取ttyACM0的序列号
std::string serial_num = get_usb_serial("ttyACM0");

这种方法无需打开串口,直接读取系统维护的设备信息,稳定且高效。

2. 通过设备协议指令读取

如果你的Roboclaw控制器支持串口指令查询序列号,可以查阅官方手册,发送对应指令后读取返回结果。比如Roboclaw通常有专用的参数查询指令,结合你现有的TimeoutSerial类实现:

// 示例:假设查询序列号的指令为0x82(需以官方手册为准)
TimeoutSerial serial("/dev/ttyACM0", 115200);
serial.setTimeout(boost::posix_time::seconds(1));

// 发送查询指令
uint8_t cmd = 0x82;
serial.write(&cmd, 1);

// 读取返回的序列号(假设返回3字节ASCII格式)
std::string serial_num = serial.readString(3);

注意:具体指令格式、波特率、返回数据长度需要严格遵循Roboclaw 2x30A的官方文档。

3. 整合到现有TimeoutSerial类

如果需要把序列号读取功能整合到你的串口类中,可以添加成员函数:

// 在TimeoutSerial类中添加成员函数
std::string TimeoutSerial::getSerialNumber() const {
    if (!isOpen()) return "";
    // 获取当前端口的设备名(如ttyACM0)
    std::string dev_path = port.native_handle();
    size_t pos = dev_path.find_last_of('/');
    std::string tty_name = dev_path.substr(pos + 1);
    // 调用第一种方法的逻辑读取序列号
    std::string sys_path = "/sys/class/tty/" + tty_name + "/device/../";
    std::ifstream serial_file(sys_path + "serial");
    std::string serial;
    if (serial_file.is_open()) {
        std::getline(serial_file, serial);
        serial_file.close();
    }
    return serial;
}

内容的提问来源于stack exchange,提问作者Marcus Barnet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 01:45:31