
无线透传告一段落。今天换六轴吐的是二进制帧不是文本。读法不对时内核计数在涨应用一个字节都拿不到。模块插上cat /dev/ttyS7 | od -An -tx1立刻看见55 51、55 52、55 53。/proc/tty/driver/serial里 ttyS7 的rx两个数字往上窜。应用这边打开成功、读循环在转打印却是空的。这不是六轴模组没吐数据也不是设备树没打开串口。数据已经进了内核 tty 缓冲。用户态用错了等待方式O_NDELAY配select在dw-apb-uart这条 DMA 申请失败、回退中断的路径上描述符的“可读”是假的。内核在收应用以为没数据。产品代号rk3568_evb板级device/board/rk/rk3568_evb/。OpenHarmony 4.1用户态 32 位 armv7 musl板上只有/system/lib/ld-musl-arm.so.1。六轴模组走 UART9600 固定上电自己吐帧大约 20 Hz不要主机发命令。1. 它怎么接到 SoC六轴模组 TX ──► SoC RX ──► dw-apb-uart ──► tty 层 ──► /dev/ttyS7 六轴模组 RX ◄── SoC TX 这条线闲着模组不听命令常见接到 uart7节点/dev/ttyS7。同一组排针的 TX/RX 只有一对。GPS、LoRa、BLE 透传也吃这组脚拔插一次一个。三颗同时插总线冲突全是噪声校验永远过不了。不要占用/dev/ttyS2。那是调试口1500000。往那里读 9600 的二进制帧串口窗口开花内核 log 也没了。电气SoC 脚 3.3 V TTL。TX 接 RX、RX 接 TX地要共。接成 TX-TX两头都在说话谁也听不见。模组侧 TX 在吐SoC 侧 RX 必须接到那根线上否则od也是空的。2. 11 字节帧不要当文本波特率9600 8N1模组手册写死改不了。上电之后自己按大约 20 Hz 往 TX 推帧主机不用write任何命令。你在串口里敲 AT、敲0xFF复位模组当噪声不会停也不会换格式。一帧 11 字节偏移内容0帧头 0x5510x51 加速度 / 0x52 角速度 / 0x53 角度2–3Xint16 小端4–5Yint16 小端6–7Zint16 小端8–9温度或保留同样 int16 小端别当加速度10checksum 前 10 字节算术和的低 8 位三种类型轮流来所以你看到的字节流是55 51 .. .. .. .. .. .. .. .. cs 55 52 .. .. .. .. .. .. .. .. cs 55 53 .. .. .. .. .. .. .. .. cs 55 51 ...校验把buf[0]到buf[9]加起来取低 8 位等于buf[10]才算一帧。错了就丢掉从下一个0x55再扫。不要“差 1 也凑合”——差 1 往往是丢了字节后面 X/Y/Z 全错位静置会读出几十 g。文本模式会毁掉这条流。0x55不是给人类看的字符。ICANON、echo、CRNL、按行fgets都会把非打印字节吃掉或改掉帧头没了后面全是碎片。必须 raw 二进制打印用十六进制。硬件流控CRTSCTS关掉CTS 悬空时控制器一直等表现为打不开或者永远读不到。115200 会拼不出校验通过的帧。控制器按 115200 采样 9600 的电平字节对不上0x55偶发对上了 checksum 也对不上。先把波特率钉死再谈解析。3. 换算以及静置 Z 接近 1 g加速度0x5116 g 量程ax raw_x / 32768 * 16 单位 g ay raw_y / 32768 * 16 az raw_z / 32768 * 16角度0x53roll raw_x / 32768 * 180 单位度 pitch raw_y / 32768 * 180 yaw raw_z / 32768 * 180角速度0x52按手册 2000 dps 量程gx raw_x / 32768 * 2000 单位 °/s gy raw_y / 32768 * 2000 gz raw_z / 32768 * 2000量程以你手上那份手册为准。16 g / 2000 dps / 180° 是这类模组最常见的三套满量程。换算系数写错静置会看到 8 g、90° 这种“刚好差一倍”的数先回头查量程别先骂 I2C——它不是 I2C。静置放桌上az接近1 g 或 -1 g这是重力不是故障。ax、ay在 0 附近小幅度抖。角速度三轴接近 0。角度里 yaw 上电能是任意值没有磁力计融合时会漂这是这类模组的常态。轻晃X/Y 加速度明显变Z 仍在 1 g 附近。若三轴一起变成几十 g是大端小端反了或把0x52的原始值用加速度公式去除。int16 小端raw (int16_t)(lo | (hi 8))hi是buf[3]不是buf[2]。4. 设备树打开 uart7别指望 DMA 名会自己冒出来板级device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dtsuart7 { status okay; pinctrl-names default; pinctrl-0 uart7m1_xfer; };改 DTS 之后dtb 要进boot_linux 和 resource。生效的那份经常是 resource 分区里的rk-kernel.dtb。只刷 Image、不刷 resource脚还是旧的。刷之前先看介质不要抄错成 eMMChdc shell cat /proc/partitionsSD 卡启动常见mmcblk1。dd必须打到/dev/block/下的分区节点不要写/dev/mmcblk1p5这种会在 tmpfs 上建普通文件的路径hdc shell dd if/data/local/tmp/boot_linux.img of/dev/block/mmcblk1p5 bs4M convfsync hdc shell dd if/data/local/tmp/resource.img of/dev/block/mmcblk1p4 bs4M convfsync hdc shell syncSoC 的 uart 节点里常见这种写法dmas有dma-names没有。dw-apb-uart去申请 DMA 通道时要按名字找tx/rx属性缺失就失败打一行类似of_dma_request_slave_channel: dma-names property of /serialfe6a0000 missing然后回退中断收发。回退本身能工作cat和od看得到数据rx计数在涨。它坑的是select的就绪语义不是物理层。不要为了消这行 log 去改 DMA先把用户态读法改对。现场核对hdc shell ls -l /dev/ttyS7 hdc shell cat /proc/tty/driver/serial期望字符设备crw-rw-rw- 1 root root 4, 71 ... /dev/ttyS7serial表要有7:行类型常见16550A。第一位是-不是c是有人echo /dev/ttyS7在 tmpfs 上建的普通文件tcgetattr报Not a tty。删掉假文件让驱动重新创c节点。HAP 不是 root。临时hdc shell chmod 0666 /dev/ttyS7重启会丢。开机脚本放device/board/rk/rk3568_evb/board_perm/board-perm.sh由*.rk30board.cfg里once 1的服务拉起来。ohos.boot.hardware必须是rk30board。5. 先证明内核在收再谈应用hdc 已连上shell 是 root。模组插着供电 3.3 V。hdc shell chmod 0666 /dev/ttyS7 hdc shell cat /proc/tty/driver/serial记下7:那一行的rx。再抓 64 字节hdc shell dd if/dev/ttyS7 bs1 count64 2/dev/null | od -An -tx1期望立刻出现55 51或55 52或55 5311 字节一组往下排。再读一次serialrx应当明显变大。这一步过了模组在吐、脚对了、uart7 probe 了、tty 层在攒字节。后面应用读空责任在等待方式不要回头改 DTS、不要换 115200、不要给模组发复位。这一步不过现象先查什么dd 卡住rx 也不涨TX/RX 没交叉或模组没电或插错排针rx 涨但 od 没有 55波特率不是 9600或旁边还插着 GPS/LoRaNo such fileDTS 没 okay或刷了 boot_linux 没刷 resourceNot a tty/dev/ttyS7 是假文件节点是 ttyS8 才有 55这块板的模组接到了 uart8改路径别改协议6. 为什么 select 会空读第一版常见写法fd open(path, O_RDWR | O_NOCTTY | O_NDELAY); /* 然后 */ select(fd 1, rfds, NULL, NULL, tv); if (FD_ISSET(fd, rfds)) n read(fd, buf, sizeof(buf));O_NDELAY在这份内核上就是非阻塞。select等可读。按 POSIX内核缓冲里有字节就该就绪。dw-apb-uart申请 DMA 失败、回退中断之后这条路径上select的可读就绪不可靠描述符不就绪或就绪之后read仍返回 0。与此同时 8250 的 flip 缓冲照收/proc/tty/driver/serial的rx在飞。于是你得到那句假现场内核有数据应用是空的。cat为什么能看见它走阻塞readtty 层有数据就拷不经过这条select判断。所以od能通不能证明select能通。不要在这条路上加usleep重试 100 次当修复。就绪位是假的睡多久都是 0。7. 改法怎么演进到现在这版① 去掉O_NDELAY改阻塞VMIN0、VTIME1。想法没数据时read最多等 0.1 秒返回 0有数据立刻返。termios 教科书就是这么写的。这块dw-apb-uart上回退中断之后VTIME不生效。read变成永久阻塞。CLI 还能 Ctrl-C页面主线程一调整页卡死按钮变灰看起来像应用死了。有数据时偶尔能读出来一碰到帧间隙或拔模块线程就钉在read上。所以 ① 只能当对照实验证明“不是没数据”不能当产品读法。② 最终O_NONBLOCKpoll带超时UI 里读放 worker 线程。poll(POLLIN, timeout_ms)在这条回退路径上能等到数据。超时返回 0调用方继续画界面、继续下一轮。read只在POLLIN之后调用非阻塞不会钉死。CLI 里主循环就是 worker。页面上把这段pollread放到独立线程不要放 UI 主线程就算poll有 50 ms 超时20 Hz 的解析仍会把一帧预算吃光点按无响应。线程里解析完再把ax/ay/az抛回 UI。两版对比写法这块板上的结果O_NDELAY selectrx 涨应用空阻塞 readVMIN0 VTIME1VTIME 无效永久阻塞卡 UIO_NONBLOCK poll 超时独立线程能收界面不卡8. 完整 C打开 ttyS7poll扫 0x55校验打印imu_uart.c/* imu_uart.c — OpenHarmony 4.1 用户态32 位 musl * imu_uart /dev/ttyS7 * 9600 8N1O_NONBLOCK poll。模组自己吐帧程序只收。 */ #include errno.h #include fcntl.h #include poll.h #include stdint.h #include stdio.h #include stdlib.h #include string.h #include termios.h #include unistd.h static int uart_open(const char *path) { int fd open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd 0) { fprintf(stderr, open %s: %s\n, path, strerror(errno)); return -1; } struct termios t; memset(t, 0, sizeof(t)); if (tcgetattr(fd, t) ! 0) { fprintf(stderr, tcgetattr: %s节点是普通文件\n, strerror(errno)); close(fd); return -1; } cfsetispeed(t, B9600); cfsetospeed(t, B9600); t.c_cflag ~CSIZE; t.c_cflag | CS8 | CLOCAL | CREAD; t.c_cflag ~PARENB; t.c_cflag ~CSTOPB; t.c_cflag ~CRTSCTS; t.c_lflag ~(ICANON | ECHO | ECHOE | ISIG); t.c_iflag ~(IXON | IXOFF | IXANY | INLCR | ICRNL | IGNCR); t.c_oflag ~OPOST; t.c_cc[VMIN] 0; t.c_cc[VTIME] 0; tcflush(fd, TCIOFLUSH); if (tcsetattr(fd, TCSANOW, t) ! 0) { fprintf(stderr, tcsetattr: %s\n, strerror(errno)); close(fd); return -1; } return fd; } static int checksum_ok(const unsigned char *f) { unsigned int s 0; int i; for (i 0; i 10; i) s f[i]; return (s 0xff) f[10]; } static int16_t le_i16(const unsigned char *p) { return (int16_t)(p[0] | (p[1] 8)); } static void on_frame(const unsigned char *f) { int16_t x le_i16(f 2); int16_t y le_i16(f 4); int16_t z le_i16(f 6); if (f[1] 0x51) { printf(acc ax%7.3f ay%7.3f az%7.3f g\n, x / 32768.0 * 16.0, y / 32768.0 * 16.0, z / 32768.0 * 16.0); } else if (f[1] 0x52) { printf(gyr gx%8.2f gy%8.2f gz%8.2f dps\n, x / 32768.0 * 2000.0, y / 32768.0 * 2000.0, z / 32768.0 * 2000.0); } else if (f[1] 0x53) { printf(ang roll%8.2f pitch%8.2f yaw%8.2f deg\n, x / 32768.0 * 180.0, y / 32768.0 * 180.0, z / 32768.0 * 180.0); } fflush(stdout); } int main(int argc, char **argv) { const char *path (argc 2) ? argv[1] : /dev/ttyS7; int fd uart_open(path); if (fd 0) return 1; unsigned char acc[64]; int nacc 0; unsigned long ok 0, bad 0; fprintf(stderr, 读 %s 9600扫 0x55。Ctrl-C 退出。\n, path); for (;;) { struct pollfd p; p.fd fd; p.events POLLIN; p.revents 0; int pr poll(p, 1, 200); if (pr 0) { if (errno EINTR) continue; fprintf(stderr, poll: %s\n, strerror(errno)); break; } if (pr 0) continue; if (!(p.revents POLLIN)) continue; unsigned char tmp[128]; int r (int)read(fd, tmp, sizeof(tmp)); if (r 0) { if (errno EAGAIN || errno EWOULDBLOCK) continue; fprintf(stderr, read: %s\n, strerror(errno)); break; } if (r 0) continue; if (nacc r (int)sizeof(acc)) { memmove(acc, acc nacc / 2, (size_t)(nacc - nacc / 2)); nacc - nacc / 2; } if (nacc r (int)sizeof(acc)) nacc 0; memcpy(acc nacc, tmp, (size_t)r); nacc r; int i 0; while (i 11 nacc) { if (acc[i] ! 0x55) { i; continue; } if (acc[i 1] ! 0x51 acc[i 1] ! 0x52 acc[i 1] ! 0x53) { i; continue; } if (!checksum_ok(acc i)) { bad; i; continue; } on_frame(acc i); ok; i 11; } if (i 0) { memmove(acc, acc i, (size_t)(nacc - i)); nacc - i; } if ((ok bad) 0 ((ok bad) % 60) 0) fprintf(stderr, ok%lu bad%lu\n, ok, bad); } close(fd); return 0; }32 位 muslOHOS$HOME/code/opcode/OpenHarmony CLANG$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang SYSROOT$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot $CLANG --targetarm-linux-ohos --sysroot$SYSROOT \ -O2 -Wall \ -Wl,--dynamic-linker/system/lib/ld-musl-arm.so.1 \ -o imu_uart imu_uart.c file imu_uart # ELF 32-bit LSB executable, ARM, interpreter /system/lib/ld-musl-arm.so.1hdc file send imu_uart /data/local/tmp/imu_uart hdc shell chmod 0755 /data/local/tmp/imu_uart hdc shell chmod 0666 /dev/ttyS7 hdc shell /data/local/tmp/imu_uart /dev/ttyS7file若是 aarch64exec失败。那是 target 写错。不要用主机的gcc。9. 验收轻晃 X/Y 变Z 仍近 1 g静置期望类似acc ax 0.020 ay -0.015 az 0.980 g gyr gx 0.30 gy -0.20 gz 0.10 dps ang roll 1.20 pitch -0.80 yaw 40.15 degaz在 0.91.1 g或 -0.9-1.1 g看模组哪面朝上。不要因为不是 0 去改公式。绕 X 轴轻轻摇ax或ay明显偏离 0az仍在 1 g 附近。角速度对应轴出现十几到几百 dps松手回落。yaw 慢漂可以接受。ok在涨、bad接近 0。bad很多、ok没有波特率不是 9600或同排针还坐着别的 UART 模组。把 GPS/LoRa 拔掉再测。打开失败对照open /dev/ttyS7: No such file or directoryDTS 没 okay或分区没刷全。tcgetattr: Not a tty假节点。open /dev/ttyS7: Permission deniedchmod 0666。hdc 是 root 通常碰不到HAP 会。程序在跑、serial的rx涨、打印没有acc你还在用旧的select二进制没把这份imu_uart推上去。hdc shell md5sum /data/local/tmp/imu_uart对一下。10. 同排针、115200、以及不要给它发命令同排针一次一颗。GPS 吐$GNxxx文本LoRa 透传是另一套波特率BLE 常见 115200。它们和六轴模组共享 TX/RX谁都不是总线主机同时插就是线与。验收六轴时旁边不要留着 GPS 的 VCC。有人把 BLE 的 115200 原样拿来。open成功poll也就绪缓冲里全是采样错的字节偶发0x55checksum 对不上bad飞涨。改回 9600同一份程序立刻出acc。不要写“先发解锁再读”。这颗模组上电自己吐。主机write进 RX模组可以忽略也可以被噪声带偏。读空时去加发送是在错误的一层打补丁。cat /dev/ttyS7当文本看会把0x55和后面的二进制搅在一起终端乱码有的字节直接丢掉。要看内容用od/hexdump或者跑上面那份 C。应用里同样禁止按行、禁止滤非 ASCII——GPS 句子那套滤法在这里会把帧头扔掉。UI 线程不要直接poll。把imu_uart这段循环搬进 worker主线程只收已经换算好的三个数。卡死一次再改回阻塞read会把 ① 的坑再踩一遍。排障顺序1. od 有没有 55 51/52/53serial 表 rx 是否在涨 2. 有数应用无是不是还在用 O_NDELAY select 3. 阻塞 read 卡 UIVTIME 在这份驱动上无效 4. 正解O_NONBLOCK poll读放 worker 5. 文本模式丢掉 0x55关 CRTSCTS 6. open 失败0666不要占用 ttyS2 7. 同排针一次一颗六轴模组系列第 47 篇 · 芯片瑞芯微 RK3568 · OpenHarmony 4.1API 11 · Linux 5.10