STM32 串口拔插后 Permission denied:启动能连,重连就没权限
设备先插着,再进软件,串口能连上。程序已经跑起来之后,把 STM32 拔出来再插回去,重连却失败了。
日志还是那句 Permission denied。第一次看见的时候,我很容易以为是重连逻辑写坏了,或者板子拔插后枚举乱了。后来才发现:软件其实已经再次扫到设备了,卡在打开 /dev/ttyACM0。板子没坏,重连也找到了口,Linux 不肯让这个普通用户进程打开新出现的那个设备节点。
这篇就当一次认真的小总结。
先看现象
典型日志长这样:
设备: /dev/ttyACM0, 描述: STM32 Virtual ComPort, VID: 1155, PID: 22336尝试连接: /dev/ttyACM0✗ 无法打开串口 /dev/ttyACM0: [Errno 13] Permission denied: '/dev/ttyACM0'两行信息已经足够定位:
VID: 1155就是十六进制0x0483,STMicroelectronicsPID: 22336就是十六进制0x5740,STM32 虚拟串口的常见产品 ID[Errno 13]就是EACCES:当前进程对这个设备文件没有读写权限
软件已经再次扫到设备了。卡点只有一个:打开 /dev/ttyACM0 这一步。不是找不到板子,也不是固件坏了。
对应关系也很干净:
| 操作 | 结果 |
|---|---|
| 设备已插入 → 启动软件 | 能连接 |
| 软件运行中拔出再插 | 没权限,不能重连 |
这是 Linux 串口设备权限问题。启动时能连,只说明当时那个节点被放过一次权;拔插之后那是另一个文件。
为什么进去能连接
启动脚本里往往已经有类似这样的一句:
chmod 666 /dev/ttyACM* /dev/ttyUSB*然后才启动你的串口程序。流程是:
- STM32 已经插着,
/dev/ttyACM0已经存在 - 启动脚本把当时那个文件改成
666(谁都能开) - 软件打开的就是这个被改过的节点 → 连接成功
这次成功只说明「启动前那一次 chmod 生效了」。它不说明用户在 dialout 组,也不说明以后拔插都有权限。
为什么软件里拔插就没权限
Linux 对 USB 串口的默认权限通常是:
crw-rw---- 1 root dialout /dev/ttyACM0也就是 0660:只有 root 和 dialout 能打开。现场跑软件的用户通常不在 dialout 组。
USB 一拔,原来的 /dev/ttyACM0 这个文件就消失,启动时改过的 666 一起没了。
再插上,内核会新建一个设备节点。名字可能还叫 ttyACM0,但是另一个文件,权限回到默认 0660。
此时:
- 启动脚本早就结束了,不会再执行
chmod 666 - 软件里的重连只是普通用户再
open()一次,没有 sudo,也不会改权限 - 用户又不在
dialout组 - 就算你刚刚把用户加进了
dialout,已经在跑的这个进程也不会自动获得新组
所以重连报 Permission denied。
启动前已插入 → 启动脚本 chmod 666(只改这一次、只改当时那个节点) → 程序打开成功
软件运行中拔出 → 旧 /dev/ttyACM0 消失,666 一起没了
再插进去 → 新节点默认 0660 → 没人再 chmod → 重连没权限软件侧会按已绑定的板子去重新打开串口,但打不开是操作系统权限,不是重连逻辑没找到设备。
为什么不能指望「名字还是 ttyACM0」
设备名可以还叫 /dev/ttyACM0。拔插后那是内核新创建的 inode。
chmod 改的是文件,不是「这个 USB 口永远 666」。没有 udev 规则时,每次插上都会重新变成 0660。
可以把它想成:你给门口那把锁换过一次钥匙,门拆掉重建之后,锁又回到出厂设置。
正确做法:启动时放一次权,拔插后让 udev 自动放权
应用代码不用改。扫描和重连逻辑已经再次找到设备了,缺的是新节点上的权限。
要同时做两件事,职责不一样:
- udev
0666:设备每次出现时自动放权,软件里拔插后重连才打得开 - 启动直前再
chmod 666一次:保证这一次进软件时,已经插着的口一定能打开
不要用 MODE="0660"。用户不在 dialout 时,拔插后还是没权限;已经在跑的进程也拿不到新加的组。
1. 写入 udev 规则
再插上来也要谁都能读写,不能依赖当前进程是否已经加入 dialout。
# ModemManager 看的是 USB 设备,不是 tty 节点ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0483", ENV{ID_MM_DEVICE_IGNORE}="1"
# STM32 虚拟串口(VID 0483,也就是日志里的 1155)SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", MODE="0666", GROUP="dialout"
# 这类口都放权;ttyUSB 是转接芯片常见的名字KERNEL=="ttyACM*", MODE="0666"KERNEL=="ttyUSB*", MODE="0666"作用拆开看:
MODE="0666":新节点一出现就是谁都能读写idVendor=="0483":对准 STM32KERNEL=="ttyACM*"/ttyUSB*:这类口都放权ID_MM_DEVICE_IGNORE=1:避免 ModemManager 把 STM32 CDC 口当成调制解调器抢走。这句要写在 USB 设备上,写在SUBSYSTEM=="tty"那一行常常不生效
0666 的代价很明确:这台机器上任何用户都能读写这个串口。开发板、工控机、自己盯着的现场机器,通常可以接受。
写入可以用 tee。普通 sudo 会把 heredoc 传给 tee;只有 sudo -S(从 stdin 读密码)才会和 heredoc 抢输入。
sudo tee /etc/udev/rules.d/99-stm32-serial.rules >/dev/null <<'EOF'ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0483", ENV{ID_MM_DEVICE_IGNORE}="1"SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", MODE="0666", GROUP="dialout"KERNEL=="ttyACM*", MODE="0666"KERNEL=="ttyUSB*", MODE="0666"EOF
sudo usermod -aG dialout "$(whoami)"sudo udevadm control --reload-rulessudo udevadm trigger --subsystem-match=ttysudo udevadm settlesudo sh -c 'for dev in /dev/ttyUSB* /dev/ttyACM*; do [ -e "$dev" ] && chmod 666 "$dev"; done'usermod -aG dialout 仍然值得做:下次重新登录后,即使规则被收回到 0660,组也能兜底。它帮不了现在已经在跑的这个进程去打开拔插后的新节点。
规则改完之后,更稳的做法是拔插一次 USB。ModemManager 如果已经占着口,还需要 sudo systemctl restart ModemManager。
2. 启动应用直前再 chmod 666 一次
保证这一次进软件时,已经插着的口一定能打开:
sudo sh -c 'for dev in /dev/ttyUSB* /dev/ttyACM*; do [ -e "$dev" ] && chmod 666 "$dev"; done'./your_serial_app两件事不要混:
- 启动时已插入 → 仍靠这次 chmod(和以前一样能连)
- 软件里再拔插 → 靠上面的 udev
0666,新节点自动有权限,重连才能成功
怎么确认已经修好
不要只看启动那一次能不能连。把设备拔出再插上,然后执行:
ls -l /dev/ttyACM0期望:
crw-rw-rw- 1 root dialout ... /dev/ttyACM0即 0666(rw-rw-rw-)。若仍是 crw-rw----(0660),软件里重连仍会没权限。
规则要写到实际跑应用的那台机器上。只改开发机无效。
如果修好后变成 busy
权限过了,日志却说端口被占用,那就是另一件事了。先查谁占着口:
sudo lsof /dev/ttyACM0现场常见的抢口选手是 ModemManager。udev 里那句 ID_MM_DEVICE_IGNORE 必须打在 USB 设备上;改完规则后重启 ModemManager 或拔插一次,已经占用的口才会放开。
收一收
这题的解题顺序其实很短:
- 先确认是启动能连、运行中拔插后才
EACCES chmod改的是当时那个文件,不是这个 USB 口永远 666- 拔插后名字可以还叫
ttyACM0,那是内核新建的 inode,权限回到0660 - 已经在跑的进程没有 sudo,也不会因为
usermod就突然进组 - 启动那一次靠 chmod;软件里再拔插,靠 udev 每次出现都设成
0666
板子常常是无辜的。真正挡路的,是那一次 chmod 走不进重连这条路。
—— Yumi
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












