首页 文章 精选 留言 我的

精选列表

搜索[开源治理],共10000篇文章
优秀的个人博客,低调大师

Zigbee透传驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

LoRa 的验收口径清楚了。今天是 Zigbee,同样是串口透传。真组网需要协调器和终端,一颗模组当不了两头。 第 41 章把 UART 打开了,第 44、45 章在同一根排针上做过 BLE 和 LoRa。这一章还是 `/dev/ttyS7`,115200 8N1,换上一颗 **Zigbee 透传模组**。 板上这一侧看起来和 LoRa 几乎没区别:`write` 几个字节,射频侧发出去;空中收到的字节,从 UART 读回来。没有 Zigbee 协议栈要你在用户态跑,没有 AF_INET,没有 socket。应用写的是串口。 先把三扇错门焊死: 1. **一块板、一颗模组,组不了网。** `write` 返回成功,只说明 UART FIFO 吃进了字节。对面没有协调器或终端,空中没有人接,读回来就是空。这不是驱动坏了。 2. **模组上的 USB 口常常只供电。** 没有 D+ / D-,插电脑认不出串口设备。通信走排针上的 TX/RX。 3. **波特率不是 9600。** GPS、六轴、LoRa 是 9600,这边默认 **115200**。写错,配置帧的 `FE` 头永远对不上。 一次能通的路径:板上打开 `ttyS7` @ 115200,十六进制发取 ID 命令,模组回 `FE` 开头的帧,串口活着。然后第二套:一颗协调器、一颗终端,同一信道,A 发 `hello`,B 在一秒内读到。 --- ![uart-map.png](https://developer.qcloudimg.com/http-save/yehe-12803057/46de1053107caf6cbd59e59791e79ac4.png) ![UART两线连接.png](https://developer.qcloudimg.com/http-save/yehe-12803057/9faf7ea4b749b1e704bbaac78d50d8ec.png) ## 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 ```dts &uart7 { status = "okay"; pinctrl-0 = ; /* GPIO3_C4 TX / GPIO3_C5 RX */ }; ``` SoC 默认 `uart7m0` 是另一组球。板级必须覆盖成 m1,否则排针是死的,`/dev/ttyS7` 节点还在,`write` 也成功——字节发到空气里的另一对脚上。 核对: ```bash 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。开机脚本: ```bash 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 主线程一起冻。 现在这套打开方式: ```c 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。交叉编译: ```bash 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: ```bash 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。 两板对发,文本模式即可: ```bash # 板 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 页复用串口控制台:设备、波特、十六进制开关、发送、单次读、连续读。 ```ts 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 写十六进制的那几行 ```c static int hexval(char c) { if (c >= '0' && c = 'a' && c = 'A' && c 模组tty波特这一侧在干什么 GPSttyS79600收 NMEA BLE 透传ttyS7115200对手机 GATT LoRattyS79600对另一颗 LoRa ZigbeettyS7115200对另一颗 Zigbee 六轴ttyS79600收 0x55 帧 语音ttyS7115200收 ASR 帧 RS485常见 ttyS8115200收发器,不是 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. 排障顺序 ```text 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

优秀的个人博客,低调大师

—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

I2C 和 GPIO 传感器先到这里。今天换串口:定位、电台、语音、仪表很多走这里。uartN 映射成 ttySN,调试口不要给应用占。 ![](https://developer.qcloudimg.com/http-save/yehe-12803057/aec04b1c1e31f730fe3278f3de5e02ee.png) ![](https://developer.qcloudimg.com/http-save/yehe-12803057/ca87a168edc14a65e11c88df5786a1dc.png) 第 33 章到第 40 章,总线是 I2C,或者干脆是一根 GPIO。从这章起换跑道:外设跟 SoC 说话,靠的是 **UART**。 有人把第 33 章的写法套过来,上来就找 `I2C_RDWR`,或者把串口当 8250 寄存器去 poke。前者完全跑偏——UART 没有寄存器地址这回事,它是字节流。后者倒接近内核实现:RK3568 上这些串口控制器,内核里报的就是 `uart:16550A`,底层驱动叫 `dw-apb-uart`,和老 PC 的 8250 兼容。**8250 这一套只在 UART 章讲。I2C 章禁止拿 termios 去套。** 这章把「怎么打开一根串口、怎么读、怎么写、怎么不把调试口占死」一次写完。后面 RS232、GPS、BLE、LoRa、六轴,全是这章工具的换皮。 --- ![](https://developer.qcloudimg.com/http-save/yehe-12803057/783a71634d2e3fcff2558e24acc8d341.png) ![](https://developer.qcloudimg.com/http-save/yehe-12803057/e1321800cb540e6c7ac0f4544d8989eb.png) ## 1\. 一张对应表,先钉死 RK3568 的 UART 控制器在设备树里叫 `uart0`、`uart2`、`uart3`、`uart7`、`uart8`。Linux 串口子系统按 **别名序号** 创节点:`uartN` → `/dev/ttySN`。 本产品 `rk3568_evb` 实际接线: | 设备树节点 | 字符设备 | 波特率(默认) | 干什么 | 应用能不能用 | | --- | --- | --- | --- | --- | | \`&uart0\` | \`/dev/ttyS0\` | 115200 | RS232,经过电平转换芯片,第 42 章 | 能 | | \`&uart2\` | \`/dev/ttyS2\` | \*\*1500000\*\* | 内核 console、U-Boot、你敲 \`#\` 的那根 | \*\*不能\*\* | | \`&uart3\` | \`/dev/ttyS3\` | 模块定 | 扩展排针上的另一路 UART | 能 | | \`&uart7\` | \`/dev/ttyS7\` | 模块定 | BLE / LoRa / GPS,看你插哪颗;同一组排针一次一个 | 能 | | \`&uart8\` | \`/dev/ttyS8\` | 模块定 | GPS 或 RS485,以 DTS 注释和实测句子为准 | 能 | 内核命令行写死了: ```bash console=ttyS2,1500000 ``` 第 05 章你已经用 1500000 8N1 看过 U-Boot。那根线就是 ttyS2。应用里 `open("/dev/ttyS2")` 再 `tcsetattr` 改成 9600,下一秒串口工具全是乱码,内核 log 也没了。调试口不是外设口。 核对活着的控制器: ```bash hdc shell "cat /proc/tty/driver/serial" ``` 期望类似: ```bash serinfo:1.0 driver revision: 0: uart:16550A mmio:0xFDD50000 irq:xx tx:0 rx:0 2: uart:16550A mmio:0xFE660000 irq:xx tx:.... rx:.... 3: uart:16550A mmio:0xFE650000 irq:xx tx:0 rx:0 7: uart:16550A mmio:0xFE6A0000 irq:xx tx:0 rx:0 8: uart:16550A mmio:0xFE6C0000 irq:xx tx:0 rx:0 ``` `uart:16550A` 就是 8250 兼容层。mmio 地址能对上 SoC 手册里的 UART 基址。没有那一行,节点要么 disabled,要么 probe 失败(第 42 章 uart0 被抢脚就是这个症状)。 ```bash hdc shell "ls -l /dev/ttyS0 /dev/ttyS2 /dev/ttyS3 /dev/ttyS7 /dev/ttyS8" ``` 必须是字符设备 `crw`。如果某条是普通文件 `-rw`,有人曾经 `echo hello > /dev/ttyS0`,当时节点还不存在,shell 在 tmpfs 的 `/dev` 下建了个假文件。`tcgetattr` 会报 `Not a tty`。删掉假文件,重新插模块或重启,让驱动创真正的 `c` 节点。 --- ## 2\. 通信怎么走 ```bash 模块 TX ──► SoC RX ──► dw-apb-uart ──► tty 层 ──► /dev/ttySN 模块 RX ◄── SoC TX ◄── 发送 FIFO ◄── write() ``` UART 是 **全双工字节流**。没有地址,没有寄存器指针,没有 ACK。你 `write` 几个字节,对端就该在它的 RX 上看到同样几个字节,顺序不变。协议(NMEA、AT、私有帧)是字节流上面的约定,内核不管。 电气上:SoC 脚是 3.3 V TTL。RS232 要电平转换(第 42 章)。GPS / BLE / LoRa 模块通常直接吃 3.3 V TTL,接扩展排针即可。TX 接 RX、RX 接 TX,地要共。接成 TX-TX,两头都在说话,谁也听不见。 同一组排针的 TX/RX 只有一对。BLE、LoRa、GPS 都可能接到 uart7 或 uart8,**拔插,一次一个**。三个同时插,总线冲突,全是乱码。设备树不会因为你拔了 GPS 就自动把节点改给 LoRa——软件侧打开的仍是那根 tty,只是对面那颗芯片换了。 GPS 尤其不要写死 ttyS7。有的板级 DTS 把 uart8 注释成 GPS,有的排针把 GPS 接到 uart7。正确做法:对 ttyS3 / ttyS7 / ttyS8 依次打开、读 1 秒,谁吐出 `$GNGGA` / `$GPGGA` / `$GNRMC`,谁就是 GPS。第 43 章按这个扫。 --- ## 3\. 设备树:打开哪一路 板级文件: ```bash device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dts ``` 调试口、扩展口最小集合: ```bash /* 调试串口:UART2, 1500000。应用别占。 */ &uart2 { status = "okay"; }; /* 扩展 UART:GPS / BLE / LoRa 轮流,一次一个 */ &uart7 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = ; }; /* 另一组扩展脚,视底板丝印 */ &uart3 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = ; }; &uart8 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = ; }; ``` `pinctrl-0` 决定这对 TX/RX 复用到哪组 GPIO。写错 mux,节点能 probe,脚上没波形。改完只刷 `boot_linux` 不够——dtb 在 **resource 分区**(第 11、12 章)。只刷 Image、不刷 resource,跑的还是旧脚。 uart0 的抢脚故事留给第 42 章。这章你只要保证:要用的那路 `status = "okay"`,pinctrl 是 xfer 而不是 gpio。 --- ## 4\. 打开方式:termios 8N1 原始模式 用户态打开串口,不是 `ioctl` 一个魔法数字就完了,是 **termios**。本教程统一: | 项 | 值 | 为什么 | | --- | --- | --- | | 数据位 | 8(\`CS8\`) | GPS / BLE / LoRa / RS232 默认 | | 校验 | 无(\`~PARENB\`) | 同上 | | 停止位 | 1(\`~CSTOPB\`) | 同上 | | 硬件流控 | 关(\`~CRTSCTS\`) | 模块常常没有 CTS 脚,CTS 悬空会让发送卡死 | | 规范模式 | 关(\`~ICANON\`) | 不要等换行才返回 | | 回显 | 关(\`~ECHO\`) | 否则你写下的字会混进读缓冲 | | 软件流控 | 关(\`~IXON\`) | 0x11/0x13 是数据,不是 XON/XOFF | | 输出处理 | 关(\`~OPOST\`) | 不要把 \`\\n\` 变成 \`\\r\\n\` | | \`VMIN\` | 0 | 配合非阻塞 | | \`VTIME\` | 0 | \*\*不要靠 VTIME 做超时\*\* | 打开标志:`O_RDWR | O_NOCTTY | O_NONBLOCK`。 `O_NOCTTY`:这个 fd 不要变成进程的控制终端。否则模块乱发的字符可能被当成 Ctrl+C。 `O_NONBLOCK`:`read` 没数据立刻返回。超时交给 `poll`。 ### 4.1 为什么不是阻塞 + VTIME 经典 Unix 写法:阻塞 fd,`VMIN=0`,`VTIME=1`,`read` 最多等 0.1 秒。教科书这么教。 这块板的 `dw-apb-uart` **没有实现 VTIME 超时**。你按教科书写,`read` 会永久阻塞。ArkTS 如果在 UI 线程调这个 native,整页卡死,看起来像 ANR,点停止按钮没反应。 所以:**不要阻塞 + VTIME。** ### 4.2 为什么不是 select 有人改成 `O_NDELAY` + `select`。内核 `/proc/tty/driver/serial` 的 rx 计数在飞,示波器上模块确实在吐字节,用户态 `select` 却一直说 fd 没就绪。 原因:控制器 DMA 请求失败之后回退到中断模式。这条路径上,`select` 的就绪检测不可靠。数据已经进了 tty 缓冲,用户态看不见。 **`poll` 对非阻塞串口 fd 是可靠的。** 带超时,就绪了再 `read`。这是本产品上的最终方案。第 47 章六轴、第 43 章 GPS,踩的都是这个坑。 ### 4.3 文本模式必须过滤非 ASCII GPS、BLE 在波特率不对时会吐 `0x80` 以上的脏字节。NAPI 用 `napi_create_string_utf8` 把缓冲区交给 ArkTS。非法 UTF-8 在 ArkTS 里 `split` / `startsWith` 会直接崩。 文本模式只保留: - `0x20`~`0x7E` 可打印 ASCII - `\r` `\n` `\t` 十六进制模式原样输出,不过滤。 --- ## 5\. 完整 C:uart\_tool.c 把下面整份存成 `uart_tool.c`。这是命令行工具,也是 NAPI 里那四个函数的原型。编译目标是 **32 位 musl**,板上只有 `/system/lib/ld-musl-arm.so.1`。 ```bash /* uart_tool.c — OpenHarmony 4.1 / rk3568_evb 用户态串口工具 * 8N1 原始模式,O_NONBLOCK + poll,文本过滤非 ASCII。 */ #include #include #include #include #include #include #include #include #include static int g_fd = -1; static speed_t baud_const(int b) { switch (b) { case 4800: return B4800; case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; default: return B9600; } } static int hexval(char c) { if (c >= '0' && c = 'a' && c = 'A' && c = 0) { close(g_fd); g_fd = -1; } int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd = 0) { close(g_fd); g_fd = -1; } } /* UartWrite:is_hex=1 时 data 是 "0A1B2C" 或带空格的十六进制串 */ int UartWrite(const char *data, int is_hex) { if (g_fd = len) break; int hi = hexval(data[i]); int lo = hexval(data[i + 1]); if (hi 0 && (pfd.revents & POLLIN)) r = (int)read(g_fd, rb, sizeof(rb)); if (r > 0) { if (as_hex) { int k; for (k = 0; k 0) used += n; } } else { int k; for (k = 0; k = 0x20 && c \n" " %s write text \n" " %s write hex \n" " %s read text \n" " %s read hex \n", argv0, argv0, argv0, argv0, argv0); } int main(int argc, char **argv) { if (argc /dev/null | grep ttyS2" ``` 应用误开 ttyS2 之后,你的 PuTTY 窗口会停,或者变成 9600 乱码。立刻杀那个进程,别重启盲试。 --- ## 8\. NAPI:libboard.so 应用不直接链 `uart_tool.c`,而是把四个函数暴露给 ArkTS。模块名 **`libboard.so`**,在 `oh-package.json5` 里声明。下面是 `napi_init.cpp` 里串口相关的完整实现。`g_uart` 是进程内全局:同时只开一个口。GPS 和 LoRa 不要两个页面一起 open。 ```bash /* 片段:放入 entry/src/main/cpp/napi_init.cpp * 依赖:上面的 UartOpen / UartWrite / UartRead / UartClose * 以及 */ #include "napi/native_api.h" static int g_uart = -1; /* 与工具里的 g_fd 同一角色,NAPI 单独一份 */ /* 把第 5 节四个函数拷进来,把 g_fd 改成 g_uart,或直接复用同一份 static。 */ static napi_value NapiUartOpen(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value a[2]; napi_get_cb_info(env, info, &argc, a, nullptr, nullptr); char path[64] = {0}; size_t len = 0; int baud = 9600; napi_get_value_string_utf8(env, a[0], path, sizeof(path) - 1, &len); napi_get_value_int32(env, a[1], &baud); int rc = UartOpen(path, baud); napi_value ret; napi_create_int32(env, rc, &ret); return ret; } static napi_value NapiUartClose(napi_env env, napi_callback_info info) { (void)info; UartClose(); napi_value ret; napi_create_int32(env, 0, &ret); return ret; } static napi_value NapiUartWrite(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value a[2]; napi_get_cb_info(env, info, &argc, a, nullptr, nullptr); char buf[512] = {0}; size_t len = 0; int is_hex = 0; napi_get_value_string_utf8(env, a[0], buf, sizeof(buf) - 1, &len); napi_get_value_int32(env, a[1], &is_hex); int n = UartWrite(buf, is_hex); napi_value ret; napi_create_int32(env, n, &ret); return ret; } static napi_value NapiUartRead(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value a[2]; napi_get_cb_info(env, info, &argc, a, nullptr, nullptr); int timeout_ms = 500, as_hex = 0; napi_get_value_int32(env, a[0], &timeout_ms); napi_get_value_int32(env, a[1], &as_hex); char buf[4096]; int n = UartRead(timeout_ms, as_hex, buf, sizeof(buf)); napi_value ret; napi_create_string_utf8(env, n > 0 ? buf : "", NAPI_AUTO_LENGTH, &ret); return ret; } /* Init 里注册: * { "uartOpen", NULL, NapiUartOpen, NULL, NULL, NULL, napi_default, NULL }, * { "uartClose", NULL, NapiUartClose, NULL, NULL, NULL, napi_default, NULL }, * { "uartWrite", NULL, NapiUartWrite, NULL, NULL, NULL, napi_default, NULL }, * { "uartRead", NULL, NapiUartRead, NULL, NULL, NULL, napi_default, NULL }, */ ``` 对应 `index.d.ts`: ```bash export const uartOpen: (path: string, baud: number) => number; export const uartClose: () => number; export const uartWrite: (data: string, isHex: number) => number; export const uartRead: (timeoutMs: number, asHex: number) => string; ``` **同步 `uartRead` 不要放在 UI 线程里轮询。** `poll` 100 ms × 若干次,一次调用就能吃掉 300~600 ms。`setInterval(300)` 叠上去,停止按钮点不了。连续读要么: 1. 手动点「读一次」,超时 400~600 ms; 2. 用 `napi_async_work` 把 poll+read 丢到 worker 线程,返回 Promise(第 47 章六轴会把异步版写全)。 这章先把同步版做对。页面上只提供「读一次」。 --- ## 9\. ArkTS 页面 新建一页 `UartConsole.ets`,给 GPS / BLE / LoRa 当底板。设备路径和波特率做成输入框,不要写死。排针换了,改框就能试。 ```bash import { router } from '@kit.ArkUI'; import board from 'libboard.so'; @Entry @Component struct UartConsole { @State msg: string = '填设备节点和波特率,点打开'; @State dev: string = '/dev/ttyS7'; @State baudStr: string = '9600'; @State opened: boolean = false; @State hex: boolean = false; @State out: string = ''; @State tx: string = ''; aboutToDisappear() { if (this.opened) { board.uartClose(); this.opened = false; } } build() { Column() { Text('UART 控制台').fontSize(20).fontWeight(FontWeight.Bold).margin({ bottom: 8 }) Text('同一组排针一次一个模块。ttyS2 是调试口,不要填。') .fontSize(13).fontColor('#C62828').margin({ bottom: 8 }) Row() { Text('设备').width(48) TextInput({ text: this.dev }) .onChange((v: string) => { this.dev = v; }) .layoutWeight(1) }.margin({ bottom: 6 }) Row() { Text('波特率').width(48) TextInput({ text: this.baudStr }) .onChange((v: string) => { this.baudStr = v; }) .layoutWeight(1) }.margin({ bottom: 6 }) Row() { Button(this.opened ? '关闭' : '打开').onClick(() => { this.toggle(); }) Button('读一次').margin({ left: 8 }).onClick(() => { this.readOnce(); }) Button(this.hex ? 'HEX 开' : 'HEX 关').margin({ left: 8 }) .onClick(() => { this.hex = !this.hex; }) }.margin({ bottom: 8 }) TextInput({ text: this.tx, placeholder: '要发送的内容' }) .onChange((v: string) => { this.tx = v; }) .margin({ bottom: 6 }) Button('发送').onClick(() => { this.send(); }) Text(this.msg).fontSize(14).margin({ top: 10 }).fontColor('#1565C0') Text(this.out).fontSize(13).margin({ top: 8 }).fontColor('#333') } .padding(16) .width('100%') } toggle() { if (this.opened) { board.uartClose(); this.opened = false; this.msg = '已关闭'; return; } if (this.dev.indexOf('ttyS2') >= 0) { this.msg = '拒绝:ttyS2 是调试口,换 ttyS7 / ttyS3 / ttyS8'; return; } let baud: number = parseInt(this.baudStr); if (Number.isNaN(baud)) baud = 9600; let rc: number = board.uartOpen(this.dev, baud); if (rc === 0) { this.opened = true; this.msg = '已打开 ' + this.dev + ' @' + baud.toString() + '。点「读一次」,不要自动轮询(同步 uartRead 会卡住 UI)。'; } else { this.msg = '打开失败 rc=' + rc.toString() + '。查节点是不是 crw、权限 0666、DTS okay、有没有占 ttyS2。'; } } readOnce() { if (!this.opened) { this.msg = '先打开'; return; } let s: string = board.uartRead(600, this.hex ? 1 : 0); if (s.length === 0) { this.out = '(600ms 无数据。换口、换波特率、看 /proc/tty/driver/serial 的 rx)'; } else { this.out = s; } } send() { if (!this.opened) { this.msg = '先打开'; return; } let n: number = board.uartWrite(this.tx, this.hex ? 1 : 0); this.msg = 'write 返回 ' + n.toString(); } } ``` `oh-package.json5` 的 `devDependencies` 里不要漏 so。`abiFilters` 必须有 `armeabi-v7a`。只编 arm64,板上 loader 对不上。 --- ## 10\. 权限:HAP 不是 root hdc shell 是 root,`open("/dev/ttyS7")` 永远成功,会给你一种「驱动好了」的错觉。HAP 进程 uid 不是 0,默认 `600` 的 tty 节点打不开,`uartOpen` 返回 -1。 开机服务里预放权。脚本放: ```bash device/board/rk/rk3568_evb/board_perm/board-perm.sh ``` 相关片段: ```bash #!/system/bin/sh # 开机一次性。HAP 非 root,写不了 chmod,只能这里做。 chmod 0666 /dev/ttyS3 2>/dev/null chmod 0666 /dev/ttyS7 2>/dev/null chmod 0666 /dev/ttyS8 2>/dev/null # 不要 chmod ttyS2。调试口保持 root。 ``` `init.rk30board.cfg` 里注册 once 服务,`console` 起来之后跑。改脚本要重打 **vendor**,刷 `mmcblk1p8`(SD)或对应 eMMC 分区。只重装 HAP 不够。 临时调试: ```bash hdc shell "chmod 0666 /dev/ttyS7 /dev/ttyS8 /dev/ttyS3" ``` 重启就丢。不要把临时 chmod 当成交付。 SELinux 若是 enforcing,光 chmod 也不够。本教程 bring-up 阶段 `enforcing=0`。交付转 enforcing 时,tty 节点要补 te 规则,那是后话。 --- ## 11\. 同一组排针,一次一个模块 硬件就一对 TX/RX。GPS 模块、BLE 透传模块、LoRa 模块,电气上都接同一组脚时: - 软件永远打开 `/dev/ttyS7`(或你实测的那根) - 物理上插谁,谁就是对端 - 三个一起插 = 三颗 TX 并联,电平打架 页面上设备框保留 ttyS3 / ttyS7 / ttyS8,是因为底板丝印和线序会骗人,不是让你同时开三个。打开新口之前必须 `uartClose` 旧的。`g_uart` 是全局,后一次 `uartOpen` 会先关前一次,但波特率、对端芯片都变了,页面状态机要自己跟上。 RS232 走 ttyS0,不和这组排针抢。RS485 可能走 ttyS8,GPS 也可能走 ttyS8,以打开后读到的内容为准。不要把「我在某根丝印上」写进代码,**认 tty 号,认实测 rx 在涨、句子对得上的那根**。 --- ## 12\. 验收 1. `/proc/tty/driver/serial` 有 0/2/3/7/8(按你实际 okay 的集合),类型 `16550A`。 2. `/dev/ttyS7` 是 `crw`,权限 `0666`(应用要用的那几根)。 3. `uart_tool open /dev/ttyS7 9600` 返回 ok。 4. 插一个自己会吐数据的模块(GPS 最省事),`read text 1500` 能看到 ASCII;hex 模式能看到 `24` 开头的 `$`。 5. 应用打开 ttyS7,点「读一次」有字;打开 ttyS2 被页面拒绝。 6. 写一段短字符串,对端(另一台串口工具,或第 42 章回环)能原样收到。 有 rx 没应用数据:先 hex 读,再查 ASCII 过滤是不是把乱码滤空了——那是波特率问题,不是 read 失败。 --- ## 13\. 坑,都是真事 **占了 ttyS2。** 应用一 open,你的调试窗口死。先查代码里有没有把「默认串口」写成 ttyS2。有的模板从 PC 抄来 `/dev/ttyUSB0`,有人改成 ttyS2「反正是串口」。 **假节点。** `echo 1 > /dev/ttyS7` 在节点出现之前执行,留下普通文件。`ls -l` 看第一位是 `-` 还是 `c`。 **select 就绪假阴性。** rx 计数涨、应用永远空。改 poll。不要和驱动工程师争论「select 按 POSIX 应该行」——这块控制器的 DMA 回退路径就是这样。 **VTIME 永久阻塞。** 页面假死,hilog 里没有崩溃。把 fd 改非阻塞,超时用 poll。 **CTS 卡发送。** 开了 `CRTSCTS`,模块没有 RTS/CTS 脚,`write` 阻塞或写 0。本教程默认关硬件流控。 **写前不 flush。** 上一次模块主动上报的半帧还在缓冲,你写下一次命令,读回来的第一包是垃圾。`UartWrite` 里 `tcflush(TCIFLUSH)` 就是干这个。指纹灯「假成功」一类问题,根子经常在这。 **32 位没编对。** so 是 arm64,HAP 装上 `dlopen` 失败。`abiFilters` 加 `armeabi-v7a`。 **只 chmod 了当前启动盘以外的那份 /dev。** `/dev` 是 tmpfs,每次启动重建。必须开机脚本,不是你昨天 hdc 敲过就完了。 --- uartN 就是 ttySN。ttyS2 是 1500000 调试口,应用禁止占用。内核报 `16550A`,驱动是 `dw-apb-uart`。用户态 8N1 原始模式,`O_NONBLOCK` + `poll`,不要 select,不要 VTIME。文本模式过滤非 ASCII。同一组排针一次一个模块。权限靠开机 `chmod 0666`。 uart0 从「probe -22、没有真 ttyS0」拉起来,下一篇。GPS 的 `$GGA` 字段怎么拆,再下一篇。 ## 路径 | 路径 | 用途 | | --- | --- | | \`device/board/rk/rk3568\_evb/kernel/rk3568-evb-linux.dts\` | \`&uart2\` \`&uart3\` \`&uart7\` \`&uart8\` | | \`/dev/ttySN\` | 用户态节点 | | \`/proc/tty/driver/serial\` | 是否真 16550A、tx/rx 计数 | | \`/proc/device-tree/serial@.../status\` | 运行中的 dtb 是否 okay | | \`device/board/rk/rk3568\_evb/board\_perm/board-perm.sh\` | 开机 chmod ttyS3/7/8 | | \`entry/src/main/cpp/napi\_init.cpp\` | \`libboard.so\` 的 uartOpen/Write/Read/Close | | \`uart\_tool.c\` | 本章命令行工具,32 位 musl 交叉编译 | --- 系列第 41 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

资源下载

更多资源
Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册