news 2026/10/28 5:15:28

六轴传感器Motion驱动框架开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
六轴传感器Motion驱动框架开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

无线透传告一段落。今天换六轴:吐的是二进制帧,不是文本。读法不对时内核计数在涨,应用一个字节都拿不到。

模块插上,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。六轴模组走 UART,9600 固定,上电自己吐帧,大约 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帧头 `0x55`
1`0x51` 加速度 / `0x52` 角速度 / `0x53` 角度
2–3X,int16 小端
4–5Y,int16 小端
6–7Z,int16 小端
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

加速度(0x51),16 g 量程:

ax = raw_x / 32768 * 16 单位 g ay = raw_y / 32768 * 16 az = raw_z / 32768 * 16

角度(0x53):

roll = 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.dts
&uart7 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&uart7m1_xfer>; };

改 DTS 之后,dtb 要进boot_linux 和 resource。生效的那份经常是 resource 分区里的rk-kernel.dtb。只刷 Image、不刷 resource,脚还是旧的。

刷之前先看介质,不要抄错成 eMMC:

hdc shell "cat /proc/partitions"

SD 卡启动常见mmcblk1。dd必须打到/dev/block/下的分区节点,不要写/dev/mmcblk1p5这种会在 tmpfs 上建普通文件的路径:

hdc shell "dd if=/data/local/tmp/boot_linux.img of=/dev/block/mmcblk1p5 bs=4M conv=fsync" hdc shell "dd if=/data/local/tmp/resource.img of=/dev/block/mmcblk1p4 bs=4M conv=fsync" hdc shell "sync"

SoC 的 uart 节点里常见这种写法:dmas有,dma-names没有。dw-apb-uart去申请 DMA 通道时要按名字找"tx"/"rx",属性缺失就失败,打一行类似:

of_dma_request_slave_channel: dma-names property of '/serial@fe6a0000' 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/ttyS7

serial表要有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 bs=1 count=64 2>/dev/null | od -An -tx1"

期望立刻出现55 51或55 52或55 53,11 字节一组往下排。再读一次serial,rx应当明显变大。

这一步过了:模组在吐、脚对了、uart7 probe 了、tty 层在攒字节。后面应用读空,责任在等待方式,不要回头改 DTS、不要换 115200、不要给模组发复位。

这一步不过:

现象先查什么
`dd` 卡住,`rx` 也不涨TX/RX 没交叉,或模组没电,或插错排针
`rx` 涨但 `od` 没有 `55`波特率不是 9600,或旁边还插着 GPS/LoRa
`No such file`DTS 没 okay,或刷了 boot_linux 没刷 resource
`Not 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为什么能看见?它走阻塞read,tty 层有数据就拷,不经过这条select判断。所以od能通不能证明select能通。

不要在这条路上加usleep重试 100 次当修复。就绪位是假的,睡多久都是 0。


7. 改法怎么演进到现在这版

① 去掉O_NDELAY,改阻塞,VMIN=0、VTIME=1。

想法:没数据时read最多等 0.1 秒返回 0,有数据立刻返。termios 教科书就是这么写的。

这块dw-apb-uart上回退中断之后,VTIME不生效。read变成永久阻塞。CLI 还能 Ctrl-C,页面主线程一调,整页卡死,按钮变灰,看起来像应用死了。有数据时偶尔能读出来,一碰到帧间隙或拔模块,线程就钉在read上。

所以 ① 只能当对照实验:证明“不是没数据”,不能当产品读法。

② 最终:O_NONBLOCK+poll带超时;UI 里读放 worker 线程。

poll(POLLIN, timeout_ms)在这条回退路径上能等到数据。超时返回 0,调用方继续画界面、继续下一轮。read只在POLLIN之后调用,非阻塞,不会钉死。

CLI 里主循环就是 worker。页面上把这段poll+read放到独立线程,不要放 UI 主线程:就算poll有 50 ms 超时,20 Hz 的解析仍会把一帧预算吃光,点按无响应。线程里解析完再把ax/ay/az抛回 UI。

两版对比:

写法这块板上的结果
`O_NDELAY` + `select``rx` 涨,应用空
阻塞 `read`,`VMIN=0` `VTIME=1``VTIME` 无效,永久阻塞,卡 UI
`O_NONBLOCK` + `poll` 超时,独立线程能收,界面不卡

8. 完整 C:打开 ttyS7,poll,扫 0x55,校验,打印

imu_uart.c:

/* imu_uart.c — OpenHarmony 4.1 用户态,32 位 musl * imu_uart /dev/ttyS7 * 9600 8N1,O_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 位 musl:

OHOS=$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 --target=arm-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.1
hdc 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/ttyS7"

file若是 aarch64,exec失败。那是 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 deg

az在 0.9~1.1 g(或 -0.9~-1.1 g,看模组哪面朝上)。不要因为不是 0 去改公式。

绕 X 轴轻轻摇:ax或ay明显偏离 0,az仍在 1 g 附近。角速度对应轴出现十几到几百 dps,松手回落。yaw 慢漂可以接受。

ok在涨、bad接近 0。bad很多、ok没有:波特率不是 9600,或同排针还坐着别的 UART 模组。把 GPS/LoRa 拔掉再测。

打开失败对照:

open /dev/ttyS7: No such file or directory

DTS 没 okay,或分区没刷全。

tcgetattr: Not a tty

假节点。

open /dev/ttyS7: Permission denied

chmod 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也就绪,缓冲里全是采样错的字节,偶发0x55,checksum 对不上,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/53;serial 表 rx 是否在涨 2. 有数应用无:是不是还在用 O_NDELAY + select 3. 阻塞 read 卡 UI:VTIME 在这份驱动上无效 4. 正解:O_NONBLOCK + poll,读放 worker 5. 文本模式丢掉 0x55;关 CRTSCTS 6. open 失败:0666;不要占用 ttyS2 7. 同排针一次一颗六轴模组

系列第 47 篇 · 芯片:瑞芯微 RK3568 · OpenHarmony 4.1(API 11) · Linux 5.10

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 5:14:26

Excel基础教程:先建表格思维,再做高效数据分析

我带过不少刚入职的同事学Excel&#xff0c;十个人里有九个开口就问“VLOOKUP怎么写”“数据透视表在哪个菜单”。每次我都先让他们把手头的表格发我看看&#xff0c;结果八成是一张横跨几十列、每行记录用合并单元格排版、表头还分了两三层的“大杂烩”。函数背得再熟&#xf…

作者头像 李华
网站建设 2026/10/28 5:14:03

Moto手机应用锁设置指南:入口、分级配置与失效排查

手机递给别人看一张照片&#xff0c;对方手指一滑就点进了聊天列表&#xff1b;孩子在旁边闹着要玩手机&#xff0c;你担心他乱点支付页面&#xff1b;手机送去检修&#xff0c;回来发现相册被人翻过——这些场景我在过去几年被问过太多次&#xff0c;而绝大多数人的第一反应是…

作者头像 李华
网站建设 2026/10/28 5:13:20

STM32F103 RS485工业通信实战:Modbus RTU土壤传感器驱动与LCD闭环显示

简介&#xff1a;本资源是一份面向嵌入式初学者与STM32开发者的RS485工业通信实战指南&#xff0c;聚焦STM32F103微控制器&#xff08;Cortex-M3内核&#xff09;在土壤温湿度传感器数据采集场景中的RS485半双工通信实现。文档完整覆盖硬件原理&#xff08;DE/RE使能控制、总线…

作者头像 李华
网站建设 2026/10/28 5:13:17

无监督学习与聚类算法:原理、应用与实战指南

1. 无监督学习与聚类算法概述在机器学习领域&#xff0c;我们常常面临大量未标注的数据&#xff0c;这些数据蕴含着宝贵的信息却难以直接利用。无监督学习正是解决这一问题的利器&#xff0c;它能在没有人工标注的情况下&#xff0c;自动发现数据中的隐藏结构和模式。而聚类作为…

作者头像 李华
网站建设 2026/10/28 5:13:10

VL53L0X飞行时间测距原理与工程落地指南

简介&#xff1a;本资源是面向嵌入式开发工程师、物联网硬件爱好者及高校电子类专业学生的VL53L0X飞行时间&#xff08;ToF&#xff09;激光测距传感器中文技术文档&#xff0c;解决初学者难以理解原厂英文Datasheet与应用指南的核心痛点。文档全面覆盖器件特性、系统框图、引脚…

作者头像 李华