# 一只大疆 4G dongle，拆开发现里面跑着 Linux（一）

- URL: https://wuming.si/blog/4002/
- Author: Aaron
- Published: 2026-08-12
- Language: zh-Hans
- Categories: Technology
- Originally published at: https://blog.wo.ai/archives/4002

> 下一篇：[树莓派上的短信与 eSIM 网关](https://wuming.si/blog/4003)

大疆有一个给无人机和遥控器用的 4G 配件，一只 U 盘大小的东西，插上 SIM 卡就能让设备联网。二手市场上很便宜。买它的人大多拿它当上网卡用。

把它插到电脑上，第一印象也确实就是个上网卡：macOS 什么反应都没有，Linux 冒出一排叫 `ttyUSB0` 到 `ttyUSB3` 的设备。看上去乏善可陈。

但它里面其实是一台完整的 LTE 模组——跑着 Linux，有 root 权限的调试接口，能打电话、收发短信、读写 eSIM。接下来这个系列会把它接到树莓派、Mac、iPhone 和安卓平板上，让它真的当一部电话用。所有后面的东西，都建立在这一篇搞清楚的几件事上。

先解释两个词，后面会反复出现：

- **模组（module）**：一块完整的蜂窝通信芯片组，自己有 CPU、有系统、有射频。手机里的"基带"就是这种东西，只是被焊死在主板上。独立卖的模组通常通过 USB 或串口接到主机上，由主机发命令指挥它。
- **AT 命令**：指挥模组的语言，一种非常古老的文本协议，从上世纪的拨号猫一直用到今天。你发一行 `ATI` 过去，它回一段自我介绍。所有蜂窝模组都说这套话。

## 它到底是什么

先问它自己是谁：

```text
ATI
Baiwang
QDC507
Revision: QDC507GLEFM21
```

查下来是这样一台东西：

| 项目 | 值 |
|---|---|
| USB 标识（大疆版） | `2CA3:4006` |
| USB 标识（通用版） | `2C7C:0125`（和移远 EC25 系列同一个 ID） |
| 基带芯片 | 高通 MDM9607，Cat.4 LTE |
| 模组内部系统 | Linux，内核 3.18.44，调试服务以 root 运行 |
| AT 命令风格 | 移远（Quectel）系，`AT+QCFG`、`AT+QNETDEVCTL`、`AT+QGPS` 这一套 |

这个发现很有用：网上关于移远 EC20/EC25 的资料基本都能套用——AT 命令、USB 配置、驱动都一样。大疆版和市面上的通用版在我们要用的功能上没有区别，后面统一叫它 QDC507。

## 插上去之后，究竟露出了什么

一个 USB 设备可以同时提供好几个"接口"，就像一根线里塞了好几条独立通道。模组露出哪几条，由一个叫 `usbcfg` 的配置决定：

```text
AT+QCFG="usbcfg"
+QCFG: "usbcfg",0x2C7C,0x125,1,1,1,1,1,1,1
                             │ │ │ │ │ │ └─ USB 音频
                             │ │ │ │ │ └─── ADB
                             │ │ │ │ └───── 网卡
                             │ │ │ └─────── Modem
                             │ │ └───────── AT
                             │ └─────────── NMEA（GPS）
                             └───────────── DIAG（诊断）
```

最后两位是整个系列的钥匙。

**ADB 开着**，意味着可以像调试安卓手机那样 `adb shell` 进到模组里面的 Linux，而且直接就是 root。ADB 是安卓的调试桥，高通的模组普遍带着它。

**音频开着**，意味着模组会额外伪装成一块 USB 声卡。通话的声音将来就从这块声卡进出。

手上这只大疆版出厂就是两个都开。另一只通用版出厂是两个都关，需要先解锁：`AT+QADBKEY?` 会给一个 8 位挑战码，用 MD5-crypt 算出应答，再用 `AT+QADBKEY="…"` 提交，然后才能改 `usbcfg`。改完发 `AT+CFUN=1,1` 让模组重启，USB 会重新枚举一次，不用拔插。

还有一个细节，它决定了后面每个平台的命运：前五个接口都是 **USB 厂商自定义类**。USB 有一套标准分类（键盘是一类，U 盘是一类，声卡是一类），操作系统看到标准类就自动装驱动。厂商自定义类等于告诉系统"这是我自己的东西，你别管"。结果是：

| 平台 | 能看到什么 |
|---|---|
| Linux | `option` 驱动认领这些接口，出现 `ttyUSB0~3`，网卡另有驱动认领 |
| macOS | 没有任何驱动认领，所以 `/dev/cu.*` 里根本不会出现这只模组 |
| Android | 同样没有驱动，App 只能用 USB Host API 自己去碰 |

也就是说，在 Mac 和安卓上，那条"发 AT 命令"的常规通道从一开始就不存在。

## 绕过去：模组内部有一条通道

既然外面没有串口，那就从里面走。`adb shell` 进去看看：

```sh
adb shell
# ls /dev/smd*
/dev/smd7 ...
```

`/dev/smd7` 是模组内部直通基带 AT 解释器的一个设备节点。只要 ADB 能用，任何平台都可以这样发 AT 命令：

```sh
adb shell "cat /dev/smd7 > /tmp/at.out 2>/dev/null & CP=\$!; sleep 0.3; \
  printf 'ATI\r' > /dev/smd7; sleep 2; kill \$CP 2>/dev/null; cat /tmp/at.out"
```

看着别扭，但它管用，而且它成了后来 Mac App 和安卓 App 共同的控制通道。一个没有驱动的平台，只要能说 ADB，就能完整指挥这只模组。

## 四种联网方式

模组怎么把网络交给主机，也是可配置的：

```text
AT+QCFG="usbnet",N     改完要 AT+CFUN=1,1 重启生效
```

| N | 模式 | Linux | macOS | Android |
|---|---|---|---|---|
| 0 | QMI | 有驱动，但要自己拨号 | 没有 | 没有 |
| 1 | ECM | 有驱动 | **直接出现一块网卡** | **直接当 USB 以太网** |
| 2 | MBIM | 有驱动 | 没有 | 部分支持 |
| 3 | RNDIS | 有驱动 | 没有 | 部分支持 |

ECM 是这里面最省事的一个：模组自己做 NAT 和 DHCP，主机拿到一个 `192.168.225.x` 的地址，模组自己是 `.1`。插上就有网，什么都不用配。中国移动的卡还会直接下发原生 IPv6。

这个"省事"后面会变成一个麻烦，第五篇会讲到——它省事到你根本不知道自己的流量已经走了 SIM 卡。

## 拨通了，但没有声音

这是第一个真正让人困惑的地方。

`ATD10086;` 能拨出去，`AT+CLCC` 能查到通话处于接通状态，对面也确实接了——但两边都听不到任何声音。

原因是**信令和声音是两套完全独立的东西**。AT 命令负责信令：拨号、振铃、接通、挂断。声音走的是另一条路，而这条路默认是断的。

移远系列模组传统上有个 `AT+QPCMV` 命令，可以把通话音频从 USB 串口里吐出来。这个固件上它是个空壳：`AT+QPCMV=?` 说支持，真去执行 `AT+QPCMV=1,0` 回 `ERROR`，而用 `AT+CLAC` 列出的 246 条可用命令里根本没有它。

真正的路径在模组内部：加载语音驱动，跑一遍音频校准，再启动一个桥接程序，把高通 DSP 里的通话音频接到那块 USB 声卡上。主机侧看到的，就是一块 **8 kHz 单声道 16 bit** 的声卡——电话音质的标准规格。

这条路细节很多，留到第四篇。这里只要记住一件事：**`ATD` 只负责信令，声音要另外搭，而且每通电话之前都得检查一遍。**

## 还有几个能力

- **eSIM**：插入 eUICC 卡（可写入的 eSIM 卡）时，可以用 `AT+CCHO`、`AT+CGLA`、`AT+CCHC` 直接和卡里的管理程序对话，完整跑一遍 eSIM 下载流程。第二篇讲。
- **ISIM**：同一套命令能读取 ISIM 应用、跑 IMS 鉴权，这是 Wi-Fi 通话的基础。第三篇讲。
- **GPS**：支持，默认关。但这个功能有毒——`AT+QGPS=1` 之后，定位数据会**直接灌进 `/dev/smd7`**，把 AT 命令的回复全部淹掉，而且这个固件不支持把定位输出改到别的端口。开着 GPS 期间，通话和短信全都会乱。想用 GPS 就别想用别的。

## 刚上手就会撞到的五个坑

这些不是什么高深问题，但每一个都足够让人卡上大半天。

### USB 序列号是 256 个 0xAA

**现象**：Mac 上 `adb devices` 明明能看到设备，但程序里发出去的所有 adb 命令都像石沉大海，没有任何输出。

**原因**：这个固件的 USB 序列号字段是 256 个字节的 `0xAA`，不是合法的 UTF-8 文本。Swift 的严格解码直接返回 `nil`，之后所有命令都带着一个空序列号，静默失败。

**怎么办**：解码要宽松。更重要的是，**不要用序列号区分模组**，因为每一只都一模一样。用 `adb -t <transport_id>` 寻址，用 USB 物理路径（比如 `usb:1-1.2`）作为持久身份——transport id 每次重新枚举都会变，USB 路径不会。

### 两只一样的模组分不清

同样的原因：Linux 上两只 `2c7c:0125` 也无法靠厂商 ID、产品 ID 或序列号区分。一次重新枚举之后，两只模组的 `ttyUSB` 编号整段互换，所有写死的路径同时失效。真正的解法是按物理端口写规则，第二篇给出具体写法。

### `adb forward` 看起来成功，其实不通

`adb forward tcp:PORT dev:/dev/smd7` 能建立，返回也正常，但读写都没有任何数据。不要在它上面搭任何东西。

### 来电提示根本没发到你在读的地方

```text
AT+QURCCFG="urcport"
+QURCCFG: "urcport","usbat"
```

模组主动上报的事件（`RING` 来电、`+CLIP` 来电号码、`+CMTI` 新短信）叫 URC，而这个配置说：URC 只发到 USB 的 AT 接口。

于是，通过 ADB 读 `/dev/smd7` 的程序**永远收不到来电通知**。你的代码没错，事件发到了另一扇门。

两个办法一起用：把 URC 端口改成 `"all"`（它是掉电保存的参数，先读再改，别每次启动都写）；同时把轮询 `AT+CLCC` 和定期检查短信收件箱当作真正的事实来源，URC 只当"提醒你快去看一眼"。

### `AT+CFUN=1,1` 会清空一切

这条命令重启模组。模组的 `/tmp` 是内存盘，重启后加载进去的语音驱动、校准标记、桥接进程全部消失，USB 也重新枚举一次。

结论是：任何"装一次就好了"的设计在这里都会失败。所有状态都必须做成每次检查、按需重建。这一条贯穿整个系列。

## 这一篇的结论

| 直觉 | 实际 |
|---|---|
| 一个上网卡 | 一台跑着 Linux、带 root 调试口的 LTE 模组 |
| 没有串口就没法控制 | `adb shell` 进去，走 `/dev/smd7` |
| 拨通了就该有声音 | 声音要在模组内部另外搭一条路由 |
| 来电会有提示 | 提示发到了你没在读的那个端口 |

下一篇把它插到树莓派上，让它开始干活：收发短信、管理 eSIM、给局域网共享 4G。

## 全系列

1. [它到底是什么](https://wuming.si/blog/4002)
2. [树莓派上的短信与 eSIM 网关](https://wuming.si/blog/4003)
3. [从零实现 VoWiFi](https://wuming.si/blog/4004)
4. [USB 话机、浏览器通话与 ESP32](https://wuming.si/blog/4005)
5. [插在 Mac 上](https://wuming.si/blog/4006)
6. [iPhone 客户端](https://wuming.si/blog/4007)
7. [安卓平板直接驱动](https://wuming.si/blog/4008)

---

*这个系列是个人学习和技术研究记录。模组的语音驱动是 GPL 衍生物，不随项目分发，需要自行准备。请遵守所在地法律法规和运营商服务条款。*
