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

多Arduino(Nano Every)连接时如何程序化匹配对应ttyUSBx设备

程序化关联Arduino与ttyUSB设备的解决方案(无需udev权限)

场景说明

我有一台连接三台Arduino(Nano Every)的PC,每台Arduino运行相同程序,作为PC与对应设备的接口:

┌────────────────────────────────────┐
  │                P C                 │
  └───┬─────────────┬─────────────┬────┘
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Arduino#1 │ │ Arduino#2 │ │ Arduino#3 │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Device #1 │ │ Device #2 │ │ Device #3 │
└───────────┘ └───────────┘ └───────────┘

开发的C程序通过打开/dev/ttyUSBx设备与Arduino通信,但启动时需手动指定对应端口;使用者频繁插拔Arduino,手动查找不可行,且受限于管理员政策无法添加udev规则,需要程序化的方式自动关联Arduino与对应的ttyUSBx设备。


可行解决方案

1. 读取sysfs中的USB设备唯一标识(无需修改Arduino程序)

Linux下所有USB设备的硬件信息都存放在/sys文件系统中,可通过ttyUSBx对应的sysfs路径读取设备的序列号(每个Arduino唯一)、厂商ID、产品ID来识别设备。

实现步骤:

  • 遍历系统中所有/dev/ttyUSB*设备
  • 对每个设备,通过readlink()获取/sys/class/tty/ttyUSBx/device的实际路径,进而读取该路径下的serial文件获取设备序列号
  • 预先记录三台Arduino的序列号,在程序中匹配即可关联对应ttyUSBx

C代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glob.h>

#define SYSFS_BUF_SIZE 256

void get_arduino_serial(const char* tty_dev, char* serial_buf, size_t buf_len) {
    char sysfs_link[SYSFS_BUF_SIZE];
    char device_path[SYSFS_BUF_SIZE];
    // 构造sysfs符号链接路径(去掉/dev/前缀)
    snprintf(sysfs_link, sizeof(sysfs_link), "/sys/class/tty/%s/device", tty_dev + 5);
    
    ssize_t link_len = readlink(sysfs_link, device_path, sizeof(device_path) - 1);
    if (link_len == -1) {
        perror("readlink failed");
        strcpy(serial_buf, "ERROR");
        return;
    }
    device_path[link_len] = '\0';

    // 构造序列号文件路径
    char serial_path[SYSFS_BUF_SIZE];
    snprintf(serial_path, sizeof(serial_path), "/sys/%s/serial", device_path);
    
    FILE* serial_file = fopen(serial_path, "r");
    if (!serial_file) {
        perror("fopen serial file failed");
        strcpy(serial_buf, "ERROR");
        return;
    }
    
    fgets(serial_buf, buf_len, serial_file);
    fclose(serial_file);
    // 去除换行符
    serial_buf[strcspn(serial_buf, "\n")] = '\0';
}

int main() {
    glob_t usb_devs;
    int glob_ret = glob("/dev/ttyUSB*", GLOB_NOSORT, NULL, &usb_devs);
    if (glob_ret != 0) {
        perror("glob failed to find ttyUSB devices");
        return 1;
    }

    // 预存三台Arduino的序列号
    const char* target_serials[] = {"SERIAL_1", "SERIAL_2", "SERIAL_3"};

    for (size_t i = 0; i < usb_devs.gl_pathc; i++) {
        char serial[64];
        get_arduino_serial(usb_devs.gl_pathv[i], serial, sizeof(serial));
        
        printf("Found %s, serial: %s\n", usb_devs.gl_pathv[i], serial);
        // 匹配预存序列号,关联对应设备
        for (size_t j = 0; j < 3; j++) {
            if (strcmp(serial, target_serials[j]) == 0) {
                printf("Matched Device #%zu\n", j+1);
                // 这里可以保存该tty设备与Device的映射关系
            }
        }
    }

    globfree(&usb_devs);
    return 0;
}

2. 自定义串口握手协议(需修改Arduino程序)

如果可以修改Arduino的程序,可给每台设备烧录唯一标识(比如写入EEPROM或直接硬编码),在串口连接建立后通过握手指令获取标识,从而关联ttyUSBx。

Arduino端代码示例:

#include <EEPROM.h>

// 每台Arduino烧录不同的设备ID,比如DEVICE_1/DEVICE_2/DEVICE_3
#define DEVICE_ID "DEVICE_1"
#define EEPROM_ADDR 0

void setup() {
  Serial.begin(9600);
  while (!Serial); // 等待串口连接
  
  // 首次烧录时写入EEPROM(之后可注释)
  // EEPROM.put(EEPROM_ADDR, DEVICE_ID);
  // EEPROM.commit();
}

void loop() {
  if (Serial.available() > 0) {
    String cmd = Serial.readStringUntil('\n');
    if (cmd.trim() == "IDENTIFY") {
      // 从EEPROM读取ID(如果写入过)
      // char stored_id[10];
      // EEPROM.get(EEPROM_ADDR, stored_id);
      // Serial.println(stored_id);
      
      // 直接发送硬编码ID
      Serial.println(DEVICE_ID);
    }
  }
}

C程序端识别代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glob.h>
#include <fcntl.h>
#include <termios.h>

int open_serial_port(const char* dev_path) {
    int fd = open(dev_path, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("Failed to open serial port");
        return -1;
    }

    // 配置串口参数(9600波特率,8位数据,无校验,1停止位)
    struct termios serial_opts;
    tcgetattr(fd, &serial_opts);
    cfsetispeed(&serial_opts, B9600);
    cfsetospeed(&serial_opts, B9600);
    serial_opts.c_cflag |= (CLOCAL | CREAD);
    serial_opts.c_cflag &= ~PARENB;
    serial_opts.c_cflag &= ~CSTOPB;
    serial_opts.c_cflag &= ~CSIZE;
    serial_opts.c_cflag |= CS8;
    tcsetattr(fd, TCSANOW, &serial_opts);

    sleep(2); // 等待Arduino重启完成
    return fd;
}

void get_device_id(int serial_fd, char* id_buf, size_t buf_len) {
    // 发送识别指令
    write(serial_fd, "IDENTIFY\n", 9);
    usleep(150000); // 等待设备响应

    ssize_t read_len = read(serial_fd, id_buf, buf_len - 1);
    if (read_len > 0) {
        id_buf[read_len] = '\0';
        id_buf[strcspn(id_buf, "\n")] = '\0'; // 去除换行符
    } else {
        strcpy(id_buf, "UNKNOWN");
    }
}

int main() {
    glob_t usb_devs;
    int glob_ret = glob("/dev/ttyUSB*", GLOB_NOSORT, NULL, &usb_devs);
    if (glob_ret != 0) {
        perror("glob failed to find ttyUSB devices");
        return 1;
    }

    const char* target_ids[] = {"DEVICE_1", "DEVICE_2", "DEVICE_3"};

    for (size_t i = 0; i < usb_devs.gl_pathc; i++) {
        int fd = open_serial_port(usb_devs.gl_pathv[i]);
        if (fd == -1) continue;

        char device_id[64];
        get_device_id(fd, device_id, sizeof(device_id));
        printf("Port %s -> Device ID: %s\n", usb_devs.gl_pathv[i], device_id);

        // 匹配目标ID,建立映射
        for (size_t j = 0; j < 3; j++) {
            if (strcmp(device_id, target_ids[j]) == 0) {
                printf("Associated with Device #%zu\n", j+1);
            }
        }

        close(fd);
    }

    globfree(&usb_devs);
    return 0;
}

3. 通过USB物理端口路径识别(适合固定插拔位置)

如果使用者会固定将Arduino插入同一物理USB端口,可通过sysfs中的USB端口路径来识别:

  • 手动记录每个物理端口对应的sysfs路径(比如/sys/bus/usb/devices/usb1/1-1)
  • 程序中遍历ttyUSBx设备,找到其对应的USB端口路径,与预存路径匹配即可关联

该方法无需修改Arduino程序,但如果使用者改变插拔端口,需要重新记录路径。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 12:31:01