跳转至

Linux 基础

工控机上的操作系统,你必须熟悉它

你的视觉程序最终跑在一台 Ubuntu 工控机上——它是一台没有显示器、没有键盘的微型电脑,装在机器人里。所有操作都要通过 SSH 在终端里完成。这一章帮你快速掌握 Linux 中最常用、最有用的知识。

Mac 用户请注意

本章内容主要面向远程 Linux 工控机操作。Mac 用户通过 SSH 连接工控机时,以下命令在远程 Linux 上执行。本机 Mac 操作的等价命令会在各节标注。

本章目标

  • 理解 Linux 文件系统结构,知道去哪里找设备、改配置
  • 掌握文件权限,解决 Permission denied 问题
  • 熟练使用常用命令,在终端里高效操作
  • 看懂 Shell 脚本,理解开机自启是怎么实现的
  • 学会 vim,能在远程终端里快速改配置
  • 管理进程,学会后台运行、终止、查看程序
  • 编写 udev 规则,彻底解决串口设备名漂移问题

1. Linux 文件系统

1.1 为什么视觉组需要了解文件系统?

你的视觉程序在运行时,要访问摄像头设备、读取配置文件、查找串口、写日志……所有这些都对应着文件系统里的"路径"。找不到设备、权限不够、配置文件位置不对——这些问题 90% 都和文件系统有关。

1.2 核心目录速查

/                      # 根目录,一切的起点
├── dev/               # 设备文件(摄像头、串口、硬盘都在这里)
├── etc/               # 系统配置文件(udev 规则、网络配置等)
├── home/              # 用户目录(你的代码、项目都在这里)
   └── navigator/     # 你的用户目录
       ├── rm_vision/ # 视觉代码仓库
       └── ...
├── tmp/               # 临时文件,重启后清空
├── usr/               # 用户级程序和库
   ├── bin/           # 普通命令(ls, grep, vim ...)
   ├── lib/           # 库文件(.so 文件)
   └── local/         # 手动安装的程序
├── opt/               # 第三方大型软件
├── proc/              # 进程信息(虚拟文件系统)
└── var/               # 日志、缓存等经常变化的数据
    └── log/           # 系统日志

1.3 视觉组最常接触的目录

这是最重要的目录之一。Linux 把硬件设备抽象成文件,摄像头、串口、GPIO 全部出现在这里:

# 查看所有串口设备
ls /dev/ttyACM*
ls /dev/ttyUSB*

# 查看摄像头设备
ls /dev/video*

# 读取串口数据(调试时非常有用)
cat /dev/ttyACM0

设备名不固定

/dev/ttyACM0 不会永远是 ttyACM0——重启或重新插拔后,它可能变成 /dev/ttyACM1。这个问题我们会在 udev 规则 那一节解决。

macOS 的设备路径与 Linux 不同,串口设备和摄像头使用不同的命名规则:

# 查看所有串口设备
ls /dev/cu.*
ls /dev/tty.*
# 常见:/dev/cu.usbmodem*, /dev/cu.usbserial*

# 查看摄像头设备
# macOS 不使用 /dev/video*,摄像头通过 AVFoundation 访问
system_profiler SPCameraDataType

串口设备选择

macOS 上 /dev/cu.*/dev/tty.* 都能看到设备,实际连接串口时建议用 /dev/cu.*(cu = callout),它不会阻塞等待设备就绪。

所有系统级配置都在这里,你最常碰到的是:

# udev 规则目录(固定设备名用)
/etc/udev/rules.d/

# 网络配置
/etc/netplan/

# 系统启动服务
/etc/systemd/system/

# 环境变量
/etc/environment
/etc/profile

在视觉组里,你几乎每次配新机器都要改 udev 规则,这个目录要烂熟于心。

你的代码、项目、配置全在这里:

/home/navigator/             # 用户 navigator 的主目录
├── rm_ws/                   # ROS2 工作空间
   ├── src/
      ├── rm_vision/       # 视觉代码
      ├── rm_driver/       # 驱动代码
      └── rm_serial/       # 串口通信
   ├── build/
   ├── install/
   └── log/
├── .bashrc                  # 终端启动配置(source ROS2 环境变量在这里)
└── .ssh/                    # SSH 密钥

快速回到家目录

cd ~        # 直接回到家目录
cd          # 不带参数也是回家
echo $HOME  # 打印家目录路径

重启后自动清空,适合存放临时数据:

# 调试时把一帧图像存到 tmp,看完就删
cv::imwrite("/tmp/debug_frame.jpg", frame);

# ROS2 默认日志目录
ls /tmp/.ros/log/

Mac 用户通过 SSH 在远程 Linux 上操作时无需关心这些,但了解 macOS 的对应概念有助于本地开发:

Linux 路径 macOS 等价 说明
/dev/ttyACM* /dev/cu.usbmodem* 串口设备,macOS 命名不同
/dev/video* system_profiler SPCameraDataType macOS 无 /dev/video*,通过 AVFoundation 访问
/etc/udev/rules.d/ 无(macOS 没有 udev) 串口名天然区分,见 udev 节
/etc/netplan/ 无(macOS 用系统偏好设置/networksetup 网络配置方式完全不同
/etc/environment ~/.zshrc macOS 默认 shell 为 zsh
/etc/profile ~/.zshrc~/.zprofile 环境变量配置文件
apt install brew install 包管理器,需先安装 Homebrew
systemctl launchctl / brew services 服务管理

2. 文件权限

2.1 权限的基本概念

Linux 的每个文件都有三类权限,分别针对三种身份:

     身份            权限
┌──────────┐    ┌──────────┐
│  u: 所有者 │    │  r: 读   │  (4)
│  g: 用户组 │    │  w: 写   │  (2)
│  o: 其他人 │    │  x: 执行  │  (1)
└──────────┘    └──────────┘

查看权限:

$ ls -l /dev/ttyACM0
crw-rw-rw- 1 root dialout 166, 0 Jul 15 10:00 /dev/ttyACM0
#  │││││││││
#  │││││││└── 其他人: r-- (可读)
#  ││││││└─── 用户组: rw- (可读可写)
#  │││││└──── 所有者: rw- (可读可写)
#  ││││└───── 文件类型: c = 字符设备

2.2 chmod — 修改权限

# 数字法(最常用)
chmod 666 /dev/ttyACM0     # 所有人可读可写(rw-rw-rw-)
chmod 755 my_script.sh     # 所有者可读写执行,其他人可读执行
chmod 600 ~/.ssh/id_rsa    # 只有所有者可读写(私钥文件必须这样)

# 字母法
chmod u+x my_script.sh     # 给所有者加执行权限
chmod g+w my_file.txt      # 给用户组加写权限
chmod o-r my_secret.txt    # 去掉其他人的读权限

视觉组中的实际应用

# 场景1:串口权限不够,程序报错 Permission denied
sudo chmod 666 /dev/ttyACM0

# 场景2:给启动脚本加执行权限
chmod +x autostart.sh

# 场景3:保护配置文件不被误改
chmod 644 config.yaml

2.3 chown — 修改所有者

# 修改文件所有者
sudo chown navigator:navigator my_file.txt

# 递归修改目录
sudo chown -R navigator:navigator /home/navigator/rm_ws/

2.4 用户组

用户组决定了你能不能访问某个设备。这是视觉组最常遇到的权限问题:

# 查看当前用户属于哪些组
groups

# 把用户加入 dialout 组(解决串口权限问题)
sudo usermod -aG dialout navigator

# 加入 video 组(解决摄像头权限问题)
sudo usermod -aG video navigator

# 加入 plugdev 组(解决 USB 设备权限问题)
sudo usermod -aG plugdev navigator

加入用户组后需要重新登录

usermod -aG 只对新的登录会话生效。加入组后,你需要 注销并重新登录,或者重启机器,新的组权限才会生效。

macOS 没有这些用户组

macOS 没有 dialoutvideoplugdev 组。串口权限通过临时命令处理:

sudo chmod 666 /dev/cu.usbmodem*
或者使用 brew install --cask 安装相关工具后由系统自动管理权限。


3. 常用命令

3.1 文件和目录操作

# 列出文件
ls                     # 基本列表
ls -l                  # 详细信息(权限、大小、日期)
ls -la                 # 包含隐藏文件(.开头的文件)
ls -lh                 # 文件大小显示为 KB/MB

# 切换目录
cd /home/navigator/rm_ws/src/   # 绝对路径
cd rm_vision/                    # 相对路径
cd ..                            # 上一级
cd -                             # 回到上一个目录
cd ~                             # 回到家目录

# 创建/删除
mkdir -p src/my_package/         # 递归创建目录
rm old_file.txt                  # 删除文件
rm -rf build/                    # 递归强制删除(慎用!)

# 复制/移动
cp config.yaml config.yaml.bak  # 备份配置文件
mv old_name.cpp new_name.cpp    # 重命名

3.2 查看文件内容

# 查看整个文件
cat /etc/os-release              # 查看系统版本

# 查看前/后几行
head -20 my_code.cpp             # 前20行
tail -20 my_code.cpp             # 后20行
tail -f /var/log/syslog          # 实时跟踪日志更新(Ctrl+C 退出)

# 分页查看
less my_code.cpp                 # 空格翻页,q 退出

在视觉组中怎么用

# 查看串口发来的数据(调试裁判系统通信)
cat /dev/ttyACM0

# 实时跟踪 ROS2 日志,找错误信息
tail -f ~/.ros/log/latest/rosout.log

# 查看系统版本(确认 Ubuntu 版本)
cat /etc/os-release

3.3 grep — 文本搜索

grep 是你用得最多的命令之一——在代码里找关键词、在日志里找错误:

# 在当前目录及子目录中搜索(最常用)
grep -r "Topic" .                     # 搜索所有包含 "Topic" 的文件
grep -rn "serial_port" .              # 显示行号
grep -rn --include="*.cpp" "class " . # 只在 .cpp 文件中搜索
grep -rn --include="*.yaml" "topic" . # 只在配置文件中搜索

# 忽略大小写
grep -rni "error" .

# 反向搜索(找不含某个关键词的行)
grep -v "^#" config.yaml              # 过滤掉注释行,看有效配置

实战:定位问题

# 视觉程序没收到串口数据?先看看 topic 有没有发出来
ros2 topic list | grep serial

# 代码里某个 topic 叫什么名字?
grep -rn --include="*.cpp" "publish" ~/rm_ws/src/rm_vision/

# 编译报错太多?过滤只看 error
colcon build 2>&1 | grep -i error

3.4 find — 查找文件

# 按文件名查找
find / -name "ttyACM*" 2>/dev/null     # 在整个系统中找串口设备
find . -name "*.yaml"                    # 在当前目录找所有 yaml 文件
find . -name "*.cpp" -newer old.cpp      # 找比 old.cpp 更新的 cpp 文件

# 按类型查找
find . -type d -name "build"             # 只找目录
find . -type f -name "*.so"              # 只找文件

3.5 apt / dpkg — 包管理

工控机上装软件、装库全靠这两个命令:

# apt:从网络安装(最常用)
sudo apt update                      # 更新软件列表
sudo apt upgrade                     # 升级所有已安装的包
sudo apt install vim                 # 安装单个软件
sudo apt install libopencv-dev       # 安装 OpenCV 开发库
sudo apt remove vim                  # 卸载软件
sudo apt autoremove                  # 清理不再需要的依赖

# dpkg:管理本地 .deb 安装包
sudo dpkg -i package.deb             # 安装本地 deb 包
dpkg -l | grep opencv                # 查看已安装的 opencv 相关包
dpkg -L libopencv-dev                # 查看某个包装了哪些文件

apt install 报错依赖问题

# 如果遇到依赖冲突,执行下面的命令修复
sudo apt --fix-broken install

macOS 等价:Homebrew

macOS 没有 apt,使用 Homebrew 作为包管理器(需先安装:/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"):

# Homebrew 等价命令对照
brew update                       # ≈ sudo apt update
brew upgrade                      # ≈ sudo apt upgrade
brew install vim                  # ≈ sudo apt install vim
brew install opencv               # ≈ sudo apt install libopencv-dev
brew uninstall vim                # ≈ sudo apt remove vim
brew autoremove                   # ≈ sudo apt autoremove
brew list | grep opencv           # ≈ dpkg -l | grep opencv
brew info opencv                  # ≈ dpkg -L libopencv-dev(查看包信息)

本地开发 vs 远程部署

本地 Mac 上用 Homebrew 安装的软件仅供本地开发/调试使用,最终部署仍需在远程 Linux 工控机上用 apt 安装对应版本。

3.6 命令组合 — 管道与重定向

管道 | 把前一个命令的输出,作为后一个命令的输入:

# 找到所有进程中的 ros2 相关进程
ps aux | grep ros2

# 列出串口设备,只看 ttyACM 开头的
ls /dev/ | grep ttyACM

# 查看代码行数
find . -name "*.cpp" | xargs wc -l

# 把输出保存到文件
ros2 topic echo /detector/armors > armor_log.txt

4. Shell 脚本基础

4.1 什么是 Shell 脚本?

Shell 脚本就是把多条终端命令写进一个文件,然后一次性执行。视觉组里最典型的例子就是 开机自启脚本

4.2 变量

#!/bin/bash

# 定义变量(等号两边不能有空格!)
NAME="navigator"
ROS_DISTRO="humble"
WS_PATH="/home/navigator/rm_ws"

# 使用变量
echo "Hello, $NAME"
echo "ROS 版本: ${ROS_DISTRO}"

# 读取命令输出
CURRENT_DATE=$(date +%Y-%m-%d)
echo "今天是: $CURRENT_DATE"

等号两边不能有空格

NAME = "navigator"   # 错误!会被当成命令 NAME
NAME="navigator"     # 正确

4.3 条件判断

#!/bin/bash

# 判断文件是否存在
if [ -f "/dev/ttyACM0" ]; then
    echo "串口设备已连接"
else
    echo "串口设备未找到,请检查 USB 线"
    exit 1
fi

# 判断目录是否存在
if [ -d "$HOME/rm_ws/src/rm_vision" ]; then
    echo "视觉代码仓库已存在"
fi

# 比较字符串
DEVICE="ttyACM0"
if [ "$DEVICE" = "ttyACM0" ]; then
    echo "这是主串口"
fi

# 数值比较
COUNT=5
if [ $COUNT -gt 3 ]; then
    echo "进程数大于 3"
fi

常用判断条件:

条件 含义
-f file 文件存在且是普通文件
-d dir 目录存在
-e path 路径存在(文件或目录)
-x file 文件可执行
-z string 字符串为空
-n string 字符串不为空
= 字符串相等
-gt / -lt 大于 / 小于(数值)

4.4 循环

#!/bin/bash

# for 循环:遍历文件
for file in /dev/ttyACM*; do
    echo "找到设备: $file"
done

# for 循环:数字范围
for i in $(seq 1 3); do
    echo "尝试第 $i 次..."
    if [ -f "/dev/ttyACM0" ]; then
        echo "设备就绪"
        break
    fi
    sleep 1
done

# while 循环:持续监控
while true; do
    if ! pgrep -x "vision_node" > /dev/null; then
        echo "视觉节点挂了,正在重启..."
        cd /home/navigator/rm_ws
        source install/setup.bash
        ros2 run rm_vision vision_node &
    fi
    sleep 5
done

4.5 函数

#!/bin/bash

# 定义函数
start_vision() {
    echo "[$(date)] 正在启动视觉系统..."
    cd /home/navigator/rm_ws
    source install/setup.bash
    ros2 launch rm_vision vision.launch.py
}

stop_vision() {
    echo "[$(date)] 正在停止视觉系统..."
    pkill -f "vision_node"
    pkill -f "rm_vision"
}

check_device() {
    local device=$1
    if [ -e "$device" ]; then
        echo "[OK] $device 存在"
        return 0
    else
        echo "[FAIL] $device 不存在"
        return 1
    fi
}

# 使用函数
check_device "/dev/ttyACM0"
if [ $? -eq 0 ]; then
    start_vision
fi

4.6 完整示例:看懂 autostart.sh

这是一个典型的视觉系统开机自启脚本:

autostart.sh
#!/bin/bash
# ============================================
# 视觉系统自启动脚本
# 放在 /home/navigator/autostart.sh
# ============================================

# 配置区(改成你自己的路径)
WORKSPACE="/home/navigator/rm_ws"
LAUNCH_FILE="rm_vision vision.launch.py"
LOG_DIR="/home/navigator/logs"
LOG_FILE="$LOG_DIR/vision_$(date +%Y%m%d_%H%M%S).log"

# 创建日志目录
mkdir -p "$LOG_DIR"

# 等待系统完全启动
echo "[$(date)] 等待系统启动..." | tee -a "$LOG_FILE"
sleep 10

# 检查串口设备
if [ ! -e "/dev/ttyACM0" ]; then
    echo "[$(date)] 串口设备未找到,等待 5 秒..." | tee -a "$LOG_FILE"
    sleep 5
fi

# 进入工作空间,加载环境
cd "$WORKSPACE" || { echo "工作空间不存在"; exit 1; }
source /opt/ros/humble/setup.bash
source install/setup.bash

# 启动视觉系统,输出重定向到日志文件
echo "[$(date)] 启动视觉系统..." | tee -a "$LOG_FILE"
ros2 launch $LAUNCH_FILE 2>&1 | tee -a "$LOG_FILE"

逐段理解

  • #!/bin/bash — 告诉系统用 bash 来执行这个文件
  • mkdir -p-p 表示如果目录已存在不报错
  • || { ...; exit 1; } — 如果 cd 失败,执行花括号里的命令然后退出
  • source ... — 加载 ROS2 环境变量,不执行这步就找不到 ros2 命令
  • 2>&1 | tee — 把标准输出和错误输出都同时显示在屏幕和写入日志文件

5. vim 编辑器

5.1 为什么学 vim?

你通过 SSH 连接工控机时,很可能没有图形界面。需要快速改配置文件、修启动脚本,vim 是最可靠的选择——几乎所有 Linux 系统都预装了它。

5.2 三种模式

┌──────────┐    i/a/o     ┌──────────┐    Esc     ┌──────────┐
│  普通模式  │ ──────────→ │  插入模式  │ ───────→  │  普通模式  │
│ (浏览/操作) │ ←────────── │ (输入文字)  │           │           │
└──────────┘              └──────────┘           └─────┬────┘
       │                                                │
       │  :                                             │
       ▼                                                │
┌──────────┐              ┌──────────┐                  │
│  命令模式  │ ──────────→ │  普通模式  │ ←────────────────┘
│ (保存/退出) │              │           │
└──────────┘              └──────────┘

5.3 最小必会操作

# 打开文件
vim config.yaml

# 进入插入模式(开始编辑)
i       # 在光标前插入
a       # 在光标后插入
o       # 在下面新开一行

# 回到普通模式
Esc

# 保存/退出(先按 Esc,再输入冒号命令)
:w      # 保存
:q      # 退出(如果有未保存修改会提示)
:wq     # 保存并退出
:q!     # 强制退出不保存

# 普通模式下的基本操作
dd      # 删除当前行
yy      # 复制当前行
p       # 粘贴
u       # 撤销
/keyword # 搜索关键词(按 n 跳到下一个)

视觉组中最常见的 vim 场景

# SSH 到工控机后,修改配置文件
ssh navigator@192.168.1.10
vim ~/rm_ws/src/rm_vision/config/vision.yaml

# 修改串口波特率:找到 serial_baud: 115200 改成 921600
# 1. 按 /serial_baud 搜索
# 2. 按 i 进入插入模式
# 3. 改数字
# 4. 按 Esc,输入 :wq 保存退出

vim 新手急救

不知道自己在什么模式?按几下 Esc,然后输入 :wq 保存退出——这在任何模式下都能安全退出。


6. 进程管理

6.1 查看进程

# 查看所有进程
ps aux

# 只看和 ros2 相关的进程
ps aux | grep ros2

# 树状显示进程关系
ps auxf | grep ros2
# macOS 不支持 ps auxf 的 f 选项,可用 brew install pstree 后用 pstree 查看进程树

# 实时监控(类似任务管理器,按 q 退出)
top
htop     # 更好用的版本(需要 sudo apt install htop)

6.2 终止进程

# 优雅终止(发送 SIGTERM)
kill <PID>             # PID 用 ps aux 查看

# 强制终止(发送 SIGKILL,最后手段)
kill -9 <PID>

# 按名字杀进程
pkill -f "vision_node"        # 终止包含 vision_node 的进程
pkill -f "ros2"               # 终止所有 ros2 进程

# 终止整个 ROS2 系统
ros2 daemon stop

慎用 kill -9

kill -9 是强制杀死进程,进程来不及保存数据就被终止。正常使用应该先 kill(不带 -9),等几秒看进程是否退出,再决定要不要强制杀。

6.3 后台运行

视觉程序需要在后台持续运行,SSH 断开后也不能停:

# 最简单:在命令末尾加 &
ros2 launch rm_vision vision.launch.py &
# 注意:SSH 断开后,这个进程也会被杀掉!

# 用 nohup 防止 SSH 断开导致进程退出
nohup ros2 launch rm_vision vision.launch.py &
# 默认输出写入 nohup.out

# 指定日志文件
nohup ros2 launch rm_vision vision.launch.py > vision.log 2>&1 &

6.4 screen — 终端复用器(强烈推荐)

screen 让你创建一个"持久化终端"——即使 SSH 断开,里面运行的程序也不会停。重连后可以重新接回原来的终端。

macOS 用户推荐 tmux

macOS 上 screen 已被弃用,推荐使用功能更强大的 tmux(brew install tmux):

# tmux 常用操作(前缀键为 Ctrl+B,而非 screen 的 Ctrl+A)
tmux new -s vision          # 创建会话
tmux attach -t vision       # 重新连接会话
tmux ls                     # 查看所有会话
# Ctrl+B, D 断开会话       # ≈ screen 的 Ctrl+A, D
SSH 到远程 Linux 工控机后,如果工控机上装的是 screen,仍然用 screen 的命令即可。

# 创建一个新的 screen 会话
screen -S vision

# 在 screen 里正常操作,启动视觉程序
cd ~/rm_ws && source install/setup.bash
ros2 launch rm_vision vision.launch.py

# 断开 screen(不终止里面运行的程序)
# 按 Ctrl+A,然后按 D

# 查看所有 screen 会话
screen -ls

# 重新连接到之前的会话
screen -r vision

# 终止 screen 会话
# 在 screen 里输入 exit,或者:
screen -X -S vision quit

工控机上的典型工作流

# 1. SSH 连接工控机
ssh navigator@192.168.1.10

# 2. 创建 screen 会话运行视觉程序
screen -S vision
cd ~/rm_ws && source install/setup.bash
ros2 launch rm_vision vision.launch.py
# 按 Ctrl+A, D 断开

# 3. 再创建一个 screen 做其他事
screen -S debug
# 在这里看日志、调参数...
# 按 Ctrl+A, D 断开

# 4. 断开 SSH
exit

# 5. 第二天重新连上,接回视觉程序
ssh navigator@192.168.1.10
screen -r vision
# 视觉程序一直在跑,日志还在

6.5 ROS2 相关的进程管理

# 查看所有正在运行的节点
ros2 node list

# 查看节点的详细信息(发布/订阅的话题)
ros2 node info /vision_node

# 查看话题列表
ros2 topic list

# 查看话题是否在发布数据
ros2 topic hz /detector/armors

# 手动终止整个 ROS2 launch
pkill -f "ros2 launch"

7. udev 规则

7.1 为什么需要 udev 规则?

macOS 没有 udev

macOS 没有 udev 系统。串口设备名天然区分(/dev/cu.usbmodem*/dev/cu.usbserial*),通常不会像 Linux 那样漂移。如需固定名称,可以用 ln -s 创建符号链接。查看所有 USB 设备:system_profiler SPUSBDataType

这是视觉组最常遇到、也最让新手困惑的问题之一。

问题:工控机上有多个 USB 设备(裁判系统串口、电机调试串口等),Linux 给它们分配的名字是 /dev/ttyACM0/dev/ttyACM1……但每次重新插拔或重启,名字可能互换。你的代码写死读 /dev/ttyACM0,结果今天连的是裁判系统,明天重启后连的可能变成了调试串口。

解决:用 udev 规则,根据设备的 供应商 ID(Vendor ID)产品 ID(Product ID) 给设备固定一个名字。

7.2 第一步:找到设备的 ID

# 插入你的 USB 设备后,运行:
lsusb

输出类似:

Bus 001 Device 003: ID 1a86:7523 QinHeng Electronics CH340 serial adapter
Bus 001 Device 004: ID 0483:5740 STMicroelectronics STM32 Virtual COM Port

ID 1a86:7523 就是 VendorID:ProductID

也可以用 udevadm 获取更详细的信息:

# 先找到设备路径
udevadm info --name=/dev/ttyACM0 --attribute-walk

找类似这样的输出:

ATTRS{idVendor}=="0483"
ATTRS{idProduct}=="5740"
ATTRS{serial}=="368D38643337"

7.3 第二步:创建 udev 规则文件

sudo vim /etc/udev/rules.d/99-serial.rules

写入以下内容(根据你的设备修改):

/etc/udev/rules.d/99-serial.rules
# 裁判系统串口 — 固定为 /dev/referee
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0666", SYMLINK+="referee"

# 调试用的 CH340 串口 — 固定为 /dev/debug_serial
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE="0666", SYMLINK+="debug_serial"

# 如果有多个相同型号的设备,用 serial 号区分
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="368D38643337", MODE="0666", SYMLINK+="referee"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{serial}=="368D38643338", MODE="0666", SYMLINK+="gun_serial"

规则文件各字段含义:

字段 含义
SUBSYSTEM=="tty" 只匹配串口设备
ATTRS{idVendor} 供应商 ID(lsusb 查到的前半段)
ATTRS{idProduct} 产品 ID(lsusb 查到的后半段)
ATTRS{serial} 序列号(区分同型号设备)
MODE="0666" 设置权限为所有人可读可写
SYMLINK+="referee" 创建符号链接 /dev/referee 指向这个设备

7.4 第三步:生效规则

# 重新加载 udev 规则
sudo udevadm control --reload-rules
sudo udevadm trigger

# 验证:拔掉设备再插上
ls -l /dev/referee
# 应该显示 -> ttyACM0 或 ttyACM1(不管哪个,名字都是 referee)

7.5 在代码中使用固定设备名

config/vision.yaml
# 不再用 /dev/ttyACM0,改用固定名
serial_port: "/dev/referee"
serial_baud: 115200

效果

无论设备插在哪个 USB 口,无论重启多少次,你的代码只需要读 /dev/referee,udev 会自动帮你找到正确的设备。

7.6 常见问题排查

# 规则写好了但没生效?
# 1. 检查语法有没有写错
cat /etc/udev/rules.d/99-serial.rules

# 2. 确认规则文件名以数字开头(数字决定加载顺序)
ls /etc/udev/rules.d/

# 3. 确认规则文件权限
ls -l /etc/udev/rules.d/99-serial.rules
# 应该是 -rw-r--r--

# 4. 查看 udev 日志
udevadm monitor --environment --udev

# 5. 测试规则是否匹配某个设备
udevadm test /sys/class/tty/ttyACM0

两个相同型号设备怎么区分?

如果你有两个完全一样的 STM32 串口设备,idVendoridProduct 完全相同,必须用 serial 号区分

# 查看 serial 号
udevadm info --name=/dev/ttyACM0 | grep SERIAL

# 在规则里用 ATTRS{serial}=="xxx" 来区分

综合练习

动手试试

在 SSH 连接到工控机后,完成以下操作:

练习 1:熟悉环境

# 查看系统信息
cat /etc/os-release
uname -a

# 查看家目录结构
ls -la ~

# 查看有哪些串口设备
ls /dev/tty*

# 查看当前用户属于哪些组
groups

练习 2:代码搜索

# 在视觉代码中找到所有 .launch.py 文件
find ~/rm_ws/src -name "*.launch.py"

# 搜索代码里用了哪些 ROS2 topic
grep -rn --include="*.cpp" "create_publisher" ~/rm_ws/src/

# 搜索配置文件中的串口设置
grep -rn --include="*.yaml" "serial" ~/rm_ws/src/

练习 3:进程管理

# 启动一个 screen 会话
screen -S practice

# 在里面运行 top 查看系统资源
top

# 断开 screen
# Ctrl+A, D

# 重新连接
screen -r practice


速查表

常用命令速查

场景 命令
查看串口设备 ls /dev/ttyACM* ls /dev/ttyUSB*
给串口权限 sudo chmod 666 /dev/ttyACM0
搜索代码 grep -rn "关键词" .
查找文件 find . -name "*.cpp"
后台运行 nohup command &
创建 screen screen -S name
接回 screen screen -r name
查看进程 ps aux \| grep ros2
终止进程 pkill -f "process_name"
查看 ROS2 节点 ros2 node list
实时看日志 tail -f /path/to/log
编辑文件 vim file (i 插入, Esc, :wq 保存退出)
装软件 sudo apt install package
改权限 chmod 755 file
改所有者 sudo chown user:group file