Zigbee透传驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
LoRa 的验收口径清楚了。今天是 Zigbee,同样是串口透传。真组网需要协调器和终端,一颗模组当不了两头。
第 41 章把 UART 打开了,第 44、45 章在同一根排针上做过 BLE 和 LoRa。这一章还是 /dev/ttyS7,115200 8N1,换上一颗 Zigbee 透传模组。
板上这一侧看起来和 LoRa 几乎没区别:write 几个字节,射频侧发出去;空中收到的字节,从 UART 读回来。没有 Zigbee 协议栈要你在用户态跑,没有 AF_INET,没有 socket。应用写的是串口。
先把三扇错门焊死:
- 一块板、一颗模组,组不了网。
write返回成功,只说明 UART FIFO 吃进了字节。对面没有协调器或终端,空中没有人接,读回来就是空。这不是驱动坏了。 - 模组上的 USB 口常常只供电。 没有 D+ / D-,插电脑认不出串口设备。通信走排针上的 TX/RX。
- 波特率不是 9600。 GPS、六轴、LoRa 是 9600,这边默认 115200。写错,配置帧的
FE头永远对不上。
一次能通的路径:板上打开 ttyS7 @ 115200,十六进制发取 ID 命令,模组回 FE 开头的帧,串口活着。然后第二套:一颗协调器、一颗终端,同一信道,A 发 hello,B 在一秒内读到。


1. 板上这一侧就是串口
应用 write("/dev/ttyS7")
│ 115200 8N1 原始模式
▼
RK3568 uart7 (GPIO3_C4 TX / GPIO3_C5 RX)
│
▼
Zigbee 透传模组 UART
│ 射频,自组网
▼
另一颗模组 UART → 另一块板的 ttyS7
透传的意思:你写什么,对端读什么,字节不解释。组网、信道、短地址,模组固件自己管。用户态最多发几条厂商配置帧查 ID、查信道,不在应用里实现 IEEE 802.15.4。
和 BLE 那章的差别:BLE 对端是手机 GATT;Zigbee 对端是另一颗同协议模组。手机 nRF Connect 在这里没用。
和 LoRa 那章的共同点:单模块只能证明「UART 通了」。无线电要两套。
排针和 GPS / BLE / LoRa / 六轴 / 语音共用 uart7。一次插一颗。插着 GPS 再插 Zigbee,两颗 TX 短在一起,谁都别想通。
2. 设备树:uart7 用 m1,不是 SoC 默认的 m0
&uart7 {
status = "okay";
pinctrl-0 = <&uart7m1_xfer>; /* GPIO3_C4 TX / GPIO3_C5 RX */
};
SoC 默认 uart7m0 是另一组球。板级必须覆盖成 m1,否则排针是死的,/dev/ttyS7 节点还在,write 也成功——字节发到空气里的另一对脚上。
核对:
hdc shell "ls -l /dev/ttyS7"
hdc shell "cat /proc/tty/driver/serial"
hdc shell "cat /proc/device-tree/serial@fe6b0000/status"
要看到字符设备 crw、7: uart:16550A、status 里 okay。ttyS2 是 1500000 调试口,应用禁止占用。
HAP 不是 root。开机脚本:
chmod 0666 /dev/ttyS7 /dev/ttyS8 /dev/ttyS3
路径按教程约定放在 device/board/rk/rk3568_evb/board_perm/board-perm.sh,打进 vendor。hdc 临时 chmod,重启丢掉。
3. 打开串口:115200,关硬件流控,不要 select
第 41 章的结论这里直接用。RK3568 的 dw-apb-uart,DMA 请求失败会回退中断模式。这条回退路径上 select 的就绪不可靠:内核 rx 计数在涨,cat /dev/ttyS7 | od 能看到字节,应用 select 却永远不返回可读。
VTIME 在这份内核上也不老实,阻塞 read 可能永不返回,ArkTS 主线程一起冻。
现在这套打开方式:
int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
struct termios t;
tcgetattr(fd, &t);
cfsetispeed(&t, B115200);
cfsetospeed(&t, B115200);
t.c_cflag = (t.c_cflag & ~CSIZE) | CS8 | CLOCAL | CREAD;
t.c_cflag &= ~(PARENB | CSTOPB | CRTSCTS); /* 模组没有 RTS/CTS,开了会卡死 */
t.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
t.c_iflag &= ~(IXON | IXOFF | IXANY | INLCR | ICRNL);
t.c_oflag &= ~OPOST;
t.c_cc[VMIN] = 0;
t.c_cc[VTIME] = 0;
tcflush(fd, TCIOFLUSH);
tcsetattr(fd, TCSANOW, &t);
读用 poll(POLLIN, timeout),就绪再 read。写之前 tcflush(fd, TCIFLUSH),丢掉模组上电乱吐的半帧,否则配置帧对不齐。
NAPI 封装成 libboard.so 的 uartOpen / uartWrite / uartRead / uartReadAsync。ArkTS 主线程不要跑同步 uartRead 的长超时,六轴那章会把这个坑放大。
4. 配置帧:单模块能验的那几字节
常见 UART-Zigbee 透传模组,配置包以 FE 05 开头、FF 结尾。三句够用:
| 作用 | 十六进制 |
|---|---|
| 取 ID | `FE059021000002FF` |
| 取短地址 | `FE059021000001FF` |
| 取信道 | `FE059021000003FF` |
模组在线会回一帧同样以 FE 开头的数据。回了 = UART 通、模组活、波特率对。没回,先换 tty、换 115200,再查 TX/RX 是否交叉。
外设测试应用 Zigbee 页默认十六进制、设备 /dev/ttyS7、波特 115200。把取 ID 贴进发送框,点发送,再点读取。看到 FE 就算单模块过关。
不要在这一步要求「发出去的 hello 自己读回来」。无线不会从自己的天线绕一圈进自己的 RX。那是 RS485 A/B 短接才有的戏,第 50 章。
5. 组网:协调器 + 终端,两套
Zigbee 最小闭合是两颗模组:
- 一颗工作在 协调器(建网、分配短地址)
- 一颗工作在 终端(入网、收发)
两颗上电后固件通常会自组网,只要信道一致、距离几米内。用户态两边都当普通串口:
板 A uart_tool send "hello\n"
│
▼ 空中
板 B uart_tool recv → 读到 hello
角色怎么切,看模组说明书:有的上电就是透传、自动入网;有的要先发进配置态的 AT 或 FE 帧设角色。应用层不要假设自己在跑一个完整的 Zigbee stack。
验收我只认这一条:A 发出的那几个字节,B 原样读到。短地址、信道能读出来是加分,不是组网的判据。
只有一颗模组时,把验收口径写成「配置帧有应答」,不要写成「Zigbee 不通」。口径写错,人会在 UART 驱动里翻三天。
6. 命令行工具
和第 41、44 章同一把 uart_tool,32 位 musl。交叉编译:
CLANG=<源码>/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang
SYSROOT=<源码>/prebuilts/ohos-sdk/linux/11/native/sysroot
$CLANG --target=arm-linux-ohos --sysroot=$SYSROOT \
-Wl,--dynamic-linker=/system/lib/ld-musl-arm.so.1 \
-O2 -o uart_tool uart_tool.c
hdc file send uart_tool /data/local/tmp/uart_tool
hdc shell "chmod +x /data/local/tmp/uart_tool"
取 ID:
hdc shell "/data/local/tmp/uart_tool /dev/ttyS7 115200 hex FE059021000002FF"
hdc shell "/data/local/tmp/uart_tool /dev/ttyS7 115200 recv 2"
recv 那一下应打出 FE...。空的话把命令换成 9600 试一次——如果 9600 出字,模组被设过波特,应用默认 115200 就要改。出厂这块我用的是 115200。
两板对发,文本模式即可:
# 板 A
hdc shell "/data/local/tmp/uart_tool /dev/ttyS7 115200 send hello"
# 板 B
hdc shell "/data/local/tmp/uart_tool /dev/ttyS7 115200 recv 3"
dmesg 里出现 path="/dev/ttyS7" 和 ioctl 0x5401(TCGETS),说明 uartOpen 真的打开了,不是应用自己报了个假成功。
7. ArkTS 页:透传控制台就够
协议解析留给 GPS、指纹、语音。Zigbee 页复用串口控制台:设备、波特、十六进制开关、发送、单次读、连续读。
import board from 'libboard.so';
@State curDev: string = '/dev/ttyS7';
@State curBaud: string = '115200';
@State hex: boolean = true;
@State tx: string = 'FE059021000002FF';
@State rx: string = '(未打开)';
@State opened: boolean = false;
toggle() {
if (this.opened) {
board.uartClose();
this.opened = false;
this.rx = '(已关闭)';
return;
}
let b: number = parseInt(this.curBaud);
if (board.uartOpen(this.curDev, b) !== 0) {
this.rx = '打开失败:节点、权限、DTS、是不是占用了 ttyS2';
return;
}
this.opened = true;
this.rx = '已打开 ' + this.curDev + ' @' + b;
}
send() {
if (!this.opened) { this.rx = '先打开'; return; }
let n: number = board.uartWrite(this.tx, this.hex ? 1 : 0);
this.rx = 'TX ' + n + ' 字节';
}
readOnce() {
if (!this.opened) { this.rx = '先打开'; return; }
let s: string = board.uartRead(500, this.hex ? 1 : 0);
this.rx = s.length > 0 ? ('RX: ' + s) : '(500ms 空)';
}
连续读用 setInterval 调 uartRead(200, 1) 可以,超时别超过间隔。更稳的是 await board.uartReadAsync(200, 1),读放进 NAPI 工作线程,停按钮还能点。
isHex=1 时 C 层把 FE0590... 收成二进制再 write。文本模式会把 FE 当两个 ASCII 字母发出去,模组当垃圾。配置帧必须勾十六进制。
8. NAPI 写十六进制的那几行
static int hexval(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* uartWrite(data, isHex) */
tcflush(g_uart, TCIFLUSH);
if (isHex) {
uint8_t out[256];
int o = 0;
for (size_t i = 0; i + 1 < len && o < 256; ) {
if (buf[i] == ' ') { i++; continue; }
int hi = hexval(buf[i]), lo = hexval(buf[i + 1]);
if (hi < 0 || lo < 0) break;
out[o++] = (uint8_t)((hi << 4) | lo);
i += 2;
}
n = (int)write(g_uart, out, o);
} else {
n = (int)write(g_uart, buf, len);
}
空格可跳,便于手贴 FE 05 90 21 00 00 02 FF。奇数个十六进制字符,最后那半个丢掉——这是刻意的,不要静默补 0。
读侧 hex 模式用 %02X 拼,不要 napi_create_string_utf8 直接塞二进制,FE 后面的字节经常不是合法 UTF-8。
9. USB 口、供电、TX/RX
模组上的 Micro-USB / Type-C,原理图经常只接 VBUS 和 GND,5 V 转 3.3 V 给芯片。电脑插上去,设备管理器不会出现 COM 口。这不是驱动没装。排针:VCC、GND、TX、RX 四根,TX 接 SoC RX,RX 接 SoC TX。接成 TX-TX,两边都听不见。
5 V 和 3.3 V 别接反。UART 电平是 3.3 V。有的模组标 5 V 供电、IO 仍是 3.3 V,以丝印为准。
模组上电会从 UART 吐一串启动信息。应用如果一打开就发配置帧,可能撞上这串垃圾,应答错位。uartOpen 里的 tcflush(TCIOFLUSH) 和写前的 TCIFLUSH 就是为这个。
10. 和同排针其它模组的关系
| 模组 | tty | 波特 | 这一侧在干什么 |
|---|---|---|---|
| GPS | ttyS7 | 9600 | 收 NMEA |
| BLE 透传 | ttyS7 | 115200 | 对手机 GATT |
| LoRa | ttyS7 | 9600 | 对另一颗 LoRa |
| Zigbee | ttyS7 | 115200 | 对另一颗 Zigbee |
| 六轴 | ttyS7 | 9600 | 收 0x55 帧 |
| 语音 | ttyS7 | 115200 | 收 ASR 帧 |
| RS485 | 常见 ttyS8 | 115200 | 收发器,不是 uart7 |
应用里设备框能改。插错排针、选错 tty,write 仍然成功——成功的是另一颗 UART 控制器。Zigbee 页如果默认 ttyS7 没回 FE,先改 ttyS3 / ttyS8 试,不要先改内核。
全局一个 g_uart。打开 Zigbee 页等于关掉六轴页的 fd。这是 NAPI 实现的限制,不是硬件限制。
11. 我踩过的
单模块当回环。 发 hello,连续读空。当时把 termios、DMA、权限全查了一遍。后来拿第二颗模组,第一秒就通。单模块只验配置帧。
USB 当数据口。 插电脑没有 COM,以为模组坏了。量排针 TX,上电有波形,UART 是好的。
9600。 手顺,从 GPS 页抄过来。内核 rx 在涨,全是错位字节,拼不出 FE。改回 115200,取 ID 立刻回。
select。 和第 47 章同一根控制器。cat 看得到,应用看不到。改 poll。
CRTSCTS。 抄了某份 8250 示例。CTS 悬空,write 卡住。关硬件流控。
占用 ttyS2。 应用误填 /dev/ttyS2,调试窗口雪花,板子还活着。断电,1500000 回来。
两颗模组信道不同。 配置帧能回,对发空。先两边都读信道,不一致就按说明书拉到同一信道。自动入网的模组,靠近上电一次。
HAP 打开失败、工具成功。 权限。ls -l /dev/ttyS7 不是 0666。
12. 排障顺序
1. ls -l /dev/ttyS7 字符设备?0666?
2. cat /proc/tty/driver/serial 有 7: 16550A?
3. 排针上只留这一颗 UART 模组
4. 115200 hex 发 FE059021000002FF,2 秒内有没有 FE 应答
5. 第 4 步空:换 ttyS3 / ttyS8,换 9600 对比一次,查 TX/RX
6. 第 4 步成功、对发失败:第二套在不在、角色、信道、距离
7. 工具成功、HAP 失败:chmod、32 位 so、是不是勾了十六进制
第 4 步切开「UART」和「射频」。这一刀值回半天。
路径清单
| 用途 | 路径 |
|---|---|
| 板级 DTS uart7 | `device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts` |
| 节点 | `/dev/ttyS7` |
| 内核串口表 | `/proc/tty/driver/serial` |
| 开机放权 | `device/board/rk/rk3568_evb/board_perm/board-perm.sh` |
| NAPI | `entry/src/main/cpp/napi_init.cpp`(`libboard.so`) |
| ArkTS | `entry/src/main/ets/pages/Zigbee.ets` |
| 工具 | `/data/local/tmp/uart_tool` |
| 调试口(勿占) | `/dev/ttyS2`,1500000 |
系列第 46 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10
更多推荐



所有评论(0)