六轴传感器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--3 X,int16 小端
4--5 Y,int16 小端
6--7 Z,int16 小端
8--9 温度或保留,同样 int16 小端,别当加速度
10 checksum = 前 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

相关推荐
云上工程笔记2 小时前
大模型私有化部署平台对比:开源模型、推理引擎和硬件要求
开源
贾伟康2 小时前
【健康菜谱助手|08】HarmonyOS ArkTS 食材百科实战:实现分类、检索和详情跳转
软件测试·移动开发·harmonyos·arkts·arkui
哦***72 小时前
一开降噪耳朵就闷?耳机有耳压感是怎么回事?
华为·音频·harmonyos
马剑威(威哥爱编程)2 小时前
【HarmonyOS 7 小艺智能体实战】10:提交、审核、上架与上线后——把服务真正交给用户
人工智能·华为·harmonyos
马剑威(威哥爱编程)2 小时前
【HarmonyOS 7 小艺智能体实战】08:在真机上把它跑起来——调试与预览
人工智能·华为·harmonyos
分布式存储与RustFS2 小时前
你的网站备份只存一块盘?把 1Panel 的备份目标换成 RustFS
运维·开源·备份·对象存储·1panel·s3·rustfs
Amazingzz2 小时前
给女友做了个电子手帐项目,狠狠感动她了....
开源
智塑未来3 小时前
MOSS-Image 和 Seedream 怎么选?开源与闭源文生图模型的长文本渲染、布局控制、设计盲评与使用方式对比
开源
狂热开发者3 小时前
车载中控要给鸿蒙用户,能不能和 Android 共用一个二维码?
android·华为·harmonyos
Erishen3 小时前
进程内 ABAC 决策引擎:为了砍掉每次授权的一跳网络,我把判定塞进了站点自己的进程
架构·开源·agent