Files
eskin-finger-sdk/docs/ARCHITECTURE.md
2026-04-30 17:21:50 +08:00

1429 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Eskin Finger SDK — 架构设计文档
## 1. 概述
**Eskin Finger SDK** 是一款高性能压敏薄膜传感器数据采集 SDK用于采集手套式压力传感器阵列的分布力数据五指 + 掌心共 28 个传感器模组)。采用三层架构设计:
```
┌─────────────────────────────────────────────────┐
│ C++ Wrapper (RAII + Callback) │
│ cpp/include/eskin_finger_sdk.hpp │
│ cpp/src/eskin_finger_sdk.cpp │
├─────────────────────────────────────────────────┤
│ C ABI Layer (FFI) │
│ include/eskin_finger_sdk.h │
│ src/ffi/c_api.rs │
├─────────────────────────────────────────────────┤
│ Rust Core (Channel + Thread) │
│ src/lib.rs, device.rs, stream.rs │
│ src/channel.rs, types.rs, error.rs │
│ src/protocol.rs, src/transport.rs │
└─────────────────────────────────────────────────┘
```
- **Rust Core**UART 通信、协议解析、数据采集线程、Channel 通信、错误管理
- **C ABI Layer**:通过 `#[repr(C)]``extern "C"` 提供稳定的 FFI 接口
- **C++ Wrapper**RAII 封装、异常处理、回调机制
---
## 2. 通信协议概述
### 2.1 UART 物理层
| 参数 | 值 |
|------|-----|
| 波特率 | 115200 |
| 数据位 | 8 bit |
| 停止位 | 1 位 |
| 校验位 | NONE |
| 通信模式 | 请求-应答(主从模式) |
### 2.2 帧结构
#### 读请求帧
```
┌────────┬──────────┬────────────┬────────┬────────┬──────────┬──────────────┬───────────┬────────┐
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │功能码 │ 起始地址 │ 读取数据长度 │ CRC-8 │ │
│ 0x55AA │ 2B(LE) │ 1B │ 0x00 │ 0xFB │ 4B(LE) │ 2B(LE) │ 1B │ │
├────────┼──────────┼────────────┼────────┼────────┼──────────┼──────────────┼──────────┼────────┤
│data[0-1]│data[2-3]│ data[4] │data[5] │data[6] │data[7-10]│ data[11-12] │ data[13] │ │
└────────┴──────────┴────────────┴────────┴────────┴──────────┴──────────────┴──────────┘
```
#### 读应答帧
```
┌────────┬──────────┬────────────┬────────┬────────────┬──────────┬───────────┬──────────┬────────┬────────┐
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │ 功能码 │ 起始地址 │ 读取数据 │ CRC-8 │ 状态 │ │
│ 0xAA55 │ 2B(LE) │ 1B │ 0x00 │ 0x80+func │ 4B(LE) │ 2B(LE) │ 1B │ 1B │ │
├────────┼──────────┼────────────┼────────┼────────────┼──────────┼───────────┼──────────┼────────┤
│data[0-1]│data[2-3]│ data[4] │data[5] │ data[6] │data[7-10]│data[11-12]│data[N+13]│data[13]│ │
└────────┴──────────┴────────────┴────────┴────────────┴──────────┴───────────┴──────────┴────────┘
```
#### 写请求帧
```
┌────────┬──────────┬────────────┬────────┬────────┬──────────┬──────────────┬───────────┬────────┐
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │功能码 │ 起始地址 │ 写入数据长度 │ 写入数据 │ CRC-8 │
│ 0x55AA │ 2B(LE) │ 1B │ 0x00 │ 0x79 │ 4B(LE) │ 2B(LE) │ NB │ 1B │
├────────┼──────────┼────────────┼────────┼────────┼──────────┼──────────────┼──────────┼────────┤
│data[0-1]│data[2-3]│ data[4] │data[5] │data[6] │data[7-10]│ data[11-12] │ data[] │data[N+13]│
└────────┴──────────┴────────────┴────────┴────────┴──────────┴──────────────┴──────────┴────────┘
```
#### 写应答帧
```
┌────────┬──────────┬────────────┬────────┬────────────┬──────────┬───────────┬────────┬────────┐
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │ 功能码 │ 起始地址 │ 返回字节数 │ CRC-8 │ 状态 │
│ 0xAA55 │ 2B(LE) │ 1B │ 0x00 │ 0x80+func │ 4B(LE) │ 2B(LE) │ 1B │ 1B │
├────────┼──────────┼────────────┼────────┼────────────┼──────────┼───────────┼────────┼────────┤
│data[0-1]│data[2-3]│ data[4] │data[5] │ data[6] │data[7-10]│data[11-12]│data[14]│data[13]│
└────────┴──────────┴────────────┴────────┴────────────┴──────────┴───────────┴────────┴────────┘
```
### 2.3 状态码
| 值 | 含义 |
|----|------|
| `0x0000` | 正常(成功) |
| `0x0001` | 读取长度超限 |
| `0x0002` | 长度错误 |
| `0x0003` | 无效地址 |
| `0x0004` | 只读寄存器 |
### 2.4 功能码
| 功能码 | 方向 | 说明 |
|--------|------|------|
| `0xFB` | 请求 → | 读请求 |
| `0x79` | 请求 → | 写请求 |
| `0x80 + func` | ← 应答 | 读/写应答(`0xFF` = 读应答,`0xF9` = 写应答) |
---
## 3. 传感器模组布局
### 3.1 模组分布28 个传感器模组)
```
┌──────────────────────────────────────────────────────────────┐
│ 传感器手套布局 │
│ │
│ 大拇指 食指 中指 无名指 小拇指 │
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
│ │指甲│ │指甲│ │指甲│ │指甲│ │指甲│ │
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
│ │指尖│ │指尖│ │指尖│ │指尖│ │指尖│ │
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
│ │中节│ │中节│ │中节│ │中节│ │中节│ │
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
│ │近节│ │近节│ │近节│ │近节│ │近节│ │
│ └──┘ └──┘ └──┘ └──┘ └──┘ │
│ │
│ ┌─────────────────┐ │
│ │ 掌心 1-8 │ │
│ │ (8个模组) │ │
│ └─────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 3.2 模组编号0-27
| 编号 | 模组 | 编号 | 模组 |
|------|------|------|------|
| 0 | 大拇指-近节 | 14 | 无名指-近节 |
| 1 | 大拇指-中节 | 15 | 无名指-中节 |
| 2 | 大拇指-指尖 | 16 | 无名指-指尖 |
| 3 | 大拇指-指甲 | 17 | 无名指-指甲 |
| 4 | 食指-近节 | 18 | 小拇指-近节 |
| 5 | 食指-中节 | 19 | 小拇指-中节 |
| 6 | 食指-指尖 | 20 | 小拇指-指尖 |
| 7 | 食指-指甲 | 21 | 小拇指-指甲 |
| 8 | 中指-近节 | 22 | 掌心 1 |
| 9 | 中指-中节 | 23 | 掌心 2 |
| 10 | 中指-指尖 | 24 | 掌心 3 |
| 11 | 中指-指甲 | 25 | 掌心 4 |
| 12 | 无名指-近节 | 26 | 掌心 5 |
| 13 | 无名指-中节 | 27 | 掌心 6 |
> 注:掌心 7、掌心 8 为扩展编号 28-29部分配置可能不包含
---
## 4. 寄存器地址映射
### 4.1 系统信息寄存器
| 地址 | 名称 | 权限 | 数据类型 | 说明 |
|------|------|------|---------|------|
| `0x0000-0x0001` | 系列号 | RO | `u32` | 设备唯一序列号 |
| `0x000F` | 固件版本 | RO | `u16` | `0x0000` = 系列号,`0x0001` = 软件版本号 |
| `0x0010` | 标定组 | RO | `u16` | 标定参数组,用于确认产品状态 |
| `0x0011` | 模组有效状态 | RO | `u16` | bit0-3: 大拇指近/中/指/甲bit4-7: 食指近/中/指/甲 |
| `0x0012` | 传感器尺寸 L_LINE | RO | `u16` | 宽度阵列点数 |
| `0x0013` | 传感器尺寸 H_LINE | RO | `u16` | 长度阵列点数 |
| `0x0014` | 分布力点数(大拇指) | RO | `u16` | 分布力字节数 = 该值 × 3 |
| `0x0015` | 分布力点数(食指) | RO | `u16` | 同上 |
| `0x0016` | 分布力点数(中指) | RO | `u16` | 同上 |
| `0x0017` | 分布力点数(无名指) | RO | `u16` | 同上 |
| `0x0018` | 分布力点数(小拇指) | RO | `u16` | 同上 |
| `0x0023` | 当前模块位置 | RO | `u16` | 0-3: 大拇指4-7: 食指,... 20-27: 掌心 |
### 4.2 配置寄存器
| 地址 | 名称 | 权限 | 数据类型 | 说明 |
|------|------|------|---------|------|
| `0x0030-0x0031` | 产品配置 1 | RW | `u32` | bit0: 自动回传使能1=使能0=不使能默认0 |
| `0x0032-0x0033` | 产品配置 2 | RW | `u32` | 保留 |
| `0x0034-0x003F` | 大拇指分布力点数 | RO | `u16[6]` | 近节、中节、指尖、指甲各模组的分布力点数 |
| `0x0040-0x004B` | 食指分布力点数 | RO | `u16[6]` | 同上 |
| `0x004C-0x0057` | 中指分布力点数 | RO | `u16[6]` | 同上 |
| `0x0058-0x0059` | 无名指分布力点数 | RO | `u16[2]` | 近节、中节 |
| `0x005A-0x005B` | 无名指分布力点数 | RO | `u16[2]` | 指尖、指甲 |
| `0x005C-0x0067` | 掌心分布力点数 | RO | `u16[6]` | 掌心8-1倒序 |
### 4.3 数据寄存器
| 地址 | 名称 | 权限 | 数据大小 | 说明 |
|------|------|------|---------|------|
| `0x0500-0x05A7` | 合力数据 | RO | 168B | 28个模组每个 3×2B (fx,fy,fz 为 i16) |
| `0x0700-0x0737` | 模组错误码 | RO | 56B | 28个模组每个 2B |
| `0x1000-0x11FF` | 大拇指分布力 | RO | 变长 | 三维分布力 (fx,fy,fz 每点 3B) |
| `0x1200-0x13FF` | 食指分布力 | RO | 变长 | 同上 |
| `0x1400-0x15FF` | 中指分布力 | RO | 变长 | 同上 |
| `0x1600-0x17FF` | 无名指分布力 | RO | 变长 | 同上 |
| `0x1800-0x19FF` | 小拇指分布力 | RO | 变长 | 同上 |
| `0x1A00-0x1BFF` | 掌心分布力1-4 | RO | 变长 | 同上 |
| `0x1C00-0x1DFF` | 掌心分布力5-8 | RO | 变长 | 同上 |
| `0x2000-0x2FFF` | 处理后数值 | RO | 变长 | 原始地址 + 0x3000 |
| `0x8000-0x8FFF` | 标定参数 | RW | 变长 | 每点 32 参数,最多 128 点 |
---
## 5. 目录结构
```
eskin-finger-sdk/
├── Cargo.toml # Rust crate 配置
├── CMakeLists.txt # C++ wrapper 构建配置
├── docs/
│ └── ARCHITECTURE.md # 本文档
├── include/
│ └── eskin_finger_sdk.h # C 头文件(公共 API
├── cpp/
│ ├── include/
│ │ └── eskin_finger_sdk.hpp # C++ wrapper 头文件
│ └── src/
│ └── eskin_finger_sdk.cpp # C++ wrapper 实现
├── src/
│ ├── lib.rs # Rust 库入口
│ ├── main.rs # 示例/测试入口
│ ├── types.rs # 数据建模(传感器结构体、枚举)
│ ├── error.rs # 错误类型定义
│ ├── protocol.rs # UART 协议解析帧编解码、CRC-8-ITU
│ ├── transport.rs # 串口传输层(读写抽象)
│ ├── register.rs # 寄存器地址映射与读写封装
│ ├── device.rs # 设备管理核心
│ ├── channel.rs # Channel 设计与管理
│ ├── stream.rs # 数据流控制(分布力采集)
│ ├── config.rs # 设备配置管理
│ └── ffi/
│ ├── mod.rs # FFI 模块声明
│ └── c_api.rs # C ABI 实现
└── examples/
└── basic_usage.rs # Rust 使用示例
```
---
## 6. Crate 依赖 (Cargo.toml)
```toml
[package]
name = "eskin-finger-sdk"
version = "0.1.0"
edition = "2024"
[lib]
name = "eskin_finger_sdk"
crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
# Channel 通信 — 高性能多生产者多消费者通道
crossbeam-channel = "0.5"
# 错误处理 — 结构化错误类型
thiserror = "2"
# 日志 — 可选的日志输出
log = "0.4"
# 日志前端
fern = "0.7"
# 时间处理 — 高精度时间戳
chrono = "0.4"
# 序列化 — 配置和数据的序列化支持
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# FFI 类型支持C 类型兼容)
libc = "0.2"
# UUID — 设备序列号等唯一标识
uuid = { version = "1", features = ["v4"] }
[dev-dependencies]
# 测试工具
env_logger = "0.11"
```
### 依赖说明
| 依赖 | 用途 | 必要性 |
|------|------|--------|
| `crossbeam-channel` | 高性能 bounded/unbounded channel支持 `try_send``recv_timeout` | **核心** |
| `thiserror` | 派生 `std::error::Error`,简化错误类型定义 | **核心** |
| `log` / `fern` | 日志抽象层 + 日志前端,方便调试 | 推荐 |
| `chrono` | 高精度时间戳(微秒/纳秒级) | 推荐 |
| `serde` / `serde_json` | 配置文件序列化、调试输出 | 推荐 |
| `libc` | FFI 类型兼容(`c_char`, `c_int` 等) | **FFI 必需** |
| `uuid` | 设备唯一标识符生成 | 可选 |
### 编译产物
```toml
crate-type = ["lib", "cdylib", "staticlib"]
```
- `lib`Rust 内部使用
- `cdylib`:生成 `.dylib`/`.so`/`.dll`,供 C/C++ 动态链接
- `staticlib`:生成 `.a`/`.lib`,供 C/C++ 静态链接
---
## 7. 数据建模
### 7.1 核心数据类型
```rust
// src/types.rs
/// 三维力向量(分布力中的单个测力点)
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Force3D {
pub fx: i16, // X 方向力 (原始值)
pub fy: i16, // Y 方向力 (原始值)
pub fz: i16, // Z 方向力 (原始值)
}
/// 三维力向量(浮点版本,用于合力)
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Force3F {
pub fx: f32, // X 方向力 (N)
pub fy: f32, // Y 方向力 (N)
pub fz: f32, // Z 方向力 (N)
}
/// 传感器模组标识
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SensorModule {
// 大拇指
ThumbProximal = 0, // 大拇指近节
ThumbMiddle = 1, // 大拇指中节
ThumbTip = 2, // 大拇指指尖
ThumbNail = 3, // 大拇指指甲
// 食指
IndexProximal = 4,
IndexMiddle = 5,
IndexTip = 6,
IndexNail = 7,
// 中指
MiddleProximal = 8,
MiddleMiddle = 9,
MiddleTip = 10,
MiddleNail = 11,
// 无名指
RingProximal = 12,
RingMiddle = 13,
RingTip = 14,
RingNail = 15,
// 小拇指
PinkyProximal = 16,
PinkyMiddle = 17,
PinkyTip = 18,
PinkyNail = 19,
// 掌心
Palm1 = 20,
Palm2 = 21,
Palm3 = 22,
Palm4 = 23,
Palm5 = 24,
Palm6 = 25,
Palm7 = 26,
Palm8 = 27,
}
/// 单个传感器模组的分布力数据
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DistributionForce {
pub module: SensorModule,
/// 分布力点数(每个点 3 字节: fx, fy, fz
pub point_count: u16,
/// 分布力数据(原始 i8 值,长度 = point_count * 3
pub points: Vec<Force3D>,
}
/// 单个传感器模组的合力数据
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CombinedForce {
pub module: SensorModule,
/// X 方向合力 (i16 原始值)
pub fx: i16,
/// Y 方向合力 (i16 原始值)
pub fy: i16,
/// Z 方向合力 (i16 原始值)
pub fz: i16,
}
/// 一帧完整的传感器采样数据
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FingerSample {
/// 时间戳微秒SDK 侧采集时间)
pub timestamp_us: u64,
/// 采样序列号SDK 侧递增)
pub sequence: u32,
/// 28 个模组的合力数据
pub combined_forces: [CombinedForce; 28],
/// 28 个模组的分布力数据(可选,根据需求拉取)
pub distribution_forces: Vec<DistributionForce>,
/// 28 个模组的错误码(每个模组 2 字节)
pub module_errors: [u16; 28],
}
/// 单个测力点的简化数据(用于 C ABI 传递分布力)
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct ForcePoint {
pub fx: i8,
pub fy: i8,
pub fz: i8,
}
```
### 7.2 设备配置
```rust
// src/config.rs
/// 设备配置
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceConfig {
/// 设备地址(默认 0x34
pub device_addr: u8,
/// 是否启用自动回传模式
/// bit0: 1=使能, 0=不使能默认0
pub auto_transmit: bool,
/// 是否读取分布力数据(合力数据始终读取)
pub read_distribution: bool,
/// 样本丢弃策略
pub drop_policy: DropPolicy,
}
/// 样本丢弃策略
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DropPolicy {
/// 丢弃新数据 — 通道满时丢弃当前新样本,保留旧数据
DropNewest,
/// 丢弃旧数据 — 通道满时丢弃队列头部的旧数据,保留最新样本
DropOldest,
}
```
### 7.3 系统信息
```rust
/// 设备系统信息(从只读寄存器读取)
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
/// 系列号
pub serial_number: u32,
/// 固件版本
pub firmware_version: u16,
/// 标定组
pub calibration_group: u16,
/// 模组有效状态bit 掩码)
pub module_active_status: u16,
/// 传感器宽度阵列点数 (L_LINE)
pub l_line: u16,
/// 传感器长度阵列点数 (H_LINE)
pub h_line: u16,
/// 产品配置 1
pub product_config_1: u32,
/// 产品配置 2
pub product_config_2: u32,
}
```
### 7.4 协议帧类型
```rust
// src/protocol.rs
/// 帧起始符
pub const FRAME_START_REQUEST: u16 = 0x55AA;
pub const FRAME_START_RESPONSE: u16 = 0xAA55;
/// 功能码
pub const FUNC_READ: u8 = 0xFB;
pub const FUNC_WRITE: u8 = 0x79;
pub const FUNC_RESPONSE_READ: u8 = 0xFF; // 0x80 + 0xFB
pub const FUNC_RESPONSE_WRITE: u8 = 0xF9; // 0x80 + 0x79
/// 设备状态码(应答帧中的 status 字段)
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeviceStatus {
Success = 0x0000, // 正常
ReadLenExceeded = 0x0001, // 读取长度超限
LengthError = 0x0002, // 长度错误
InvalidAddress = 0x0003, // 无效地址
ReadOnlyRegister = 0x0004, // 只读寄存器
}
/// 读请求帧结构
pub struct ReadRequest {
pub device_addr: u8,
pub start_addr: u32,
pub read_byte_count: u16,
}
/// 写请求帧结构
pub struct WriteRequest {
pub device_addr: u8,
pub start_addr: u32,
pub data: Vec<u8>,
}
/// 读应答帧结构
pub struct ReadResponse {
pub device_addr: u8,
pub start_addr: u32,
pub data: Vec<u8>,
pub status: DeviceStatus,
}
/// 写应答帧结构
pub struct WriteResponse {
pub device_addr: u8,
pub start_addr: u32,
pub return_byte_count: u16,
pub status: DeviceStatus,
}
```
---
## 8. 错误处理
### 8.1 SDK 错误码C ABI
```rust
// src/error.rs
/// C ABI 兼容的错误码
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SdkErrorCode {
Success = 0,
InvalidPointer = 1,
DeviceNotFound = 2,
DeviceAlreadyOpen = 3,
NotInitialized = 4,
AlreadyStreaming = 5,
NotStreaming = 6,
ConfigError = 7,
IoError = 8,
Timeout = 9,
ChannelClosed = 10,
InternalError = 11,
BufferOverflow = 12,
InvalidParameter = 13,
CrcError = 14,
FrameError = 15,
ProtocolError = 16,
DeviceError = 17, // 设备返回非零状态码
}
/// Rust 内部错误类型
#[derive(Debug, thiserror::Error)]
pub enum SdkError {
#[error("Device not found: {0}")]
DeviceNotFound(String),
#[error("Device already open")]
DeviceAlreadyOpen,
#[error("SDK not initialized")]
NotInitialized,
#[error("Already streaming")]
AlreadyStreaming,
#[error("Not streaming")]
NotStreaming,
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Read timeout")]
Timeout,
#[error("Channel closed")]
ChannelClosed,
#[error("Internal error: {0}")]
InternalError(String),
#[error("Buffer overflow — dropped {0} samples")]
BufferOverflow(u64),
#[error("Invalid parameter: {0}")]
InvalidParameter(String),
#[error("CRC error: expected 0x{expected:02X}, got 0x{actual:02X}")]
CrcError { expected: u8, actual: u8 },
#[error("Frame error: {0}")]
FrameError(String),
#[error("Protocol error: {0}")]
ProtocolError(String),
#[error("Device error: status 0x{0:04X}")]
DeviceError(u16),
}
```
### 8.2 设备侧错误码
```rust
/// 设备应答帧中的状态码
impl DeviceStatus {
pub fn to_error(&self) -> Option<SdkError> {
match self {
DeviceStatus::Success => None,
DeviceStatus::ReadLenExceeded => Some(SdkError::DeviceError(0x0001)),
DeviceStatus::LengthError => Some(SdkError::DeviceError(0x0002)),
DeviceStatus::InvalidAddress => Some(SdkError::DeviceError(0x0003)),
DeviceStatus::ReadOnlyRegister => Some(SdkError::DeviceError(0x0004)),
}
}
}
```
---
## 9. CRC-8-ITU 校验
```rust
// src/protocol.rs
/// CRC-8-ITU 多项式: x^8 + x^2 + x + 1 (0x07)
/// 初始值: 0x00
pub fn crc8_itu(data: &[u8]) -> u8 {
let mut crc: u8 = 0x00;
for &byte in data {
crc ^= byte;
for _ in 0..8 {
if crc & 0x80 != 0 {
crc = (crc << 1) ^ 0x07;
} else {
crc <<= 1;
}
}
}
crc
}
```
---
## 10. Channel 设计
### 10.1 通道架构
```
┌──────────────────────┐
│ UART I/O Thread │
│ (串口数据采集线程) │
└──────┬───────┬───────┘
│ │
sample_tx event_tx
│ │
┌────────────▼─┐ ┌──▼───────────┐
│ Data Channel │ │ Event Channel │
│ (bounded) │ │ (bounded) │
│ capacity: 64 │ │ capacity: 64 │
└────────────┬─┘ └──┬────────────┘
│ │
sample_rx event_rx
│ │
┌────────────▼───────▼────────────┐
│ User-facing API │
│ read_sample() / callback │
└──────────────────────────────────┘
┌──────────────────────┐
│ User Thread │
└──────┬───────────────┘
cmd_tx
┌──────▼──────────────┐
│ Command Channel │
│ (bounded) │
│ capacity: 16 │
└──────┬───────────────┘
cmd_rx
┌──────▼──────────────┐
│ UART I/O Thread │
└──────────────────────┘
```
### 10.2 通道定义
```rust
// src/channel.rs
use crossbeam_channel::{bounded, Sender, Receiver};
use std::sync::atomic::{AtomicU64, Ordering};
/// 设备命令
#[derive(Debug, Clone)]
pub enum DeviceCommand {
StartStream,
StopStream,
SetConfig(DeviceConfig),
/// 读取指定寄存器
ReadRegister { addr: u32, length: u16 },
/// 写入指定寄存器
WriteRegister { addr: u32, data: Vec<u8> },
Shutdown,
}
/// 设备事件
#[derive(Debug, Clone)]
pub enum DeviceEvent {
Disconnected(String),
IoError(String),
ProtocolError(String),
ConfigApplied,
StreamStarted,
StreamStopped,
SampleDropped { count: u64 },
ModuleError { module: SensorModule, error_code: u16 },
}
/// 通道管理器
pub struct ChannelManager {
// 数据通道 — 传感器采样数据
pub sample_tx: Sender<FingerSample>,
pub sample_rx: Receiver<FingerSample>,
// 命令通道 — 控制命令
pub cmd_tx: Sender<DeviceCommand>,
pub cmd_rx: Receiver<DeviceCommand>,
// 事件通道 — 事件通知
pub event_tx: Sender<DeviceEvent>,
pub event_rx: Receiver<DeviceEvent>,
// 丢弃计数器
pub dropped_samples: AtomicU64,
// 丢弃策略
pub drop_policy: DropPolicy,
}
impl ChannelManager {
pub fn new(
sample_capacity: usize, // 建议 64传感器帧率较低
cmd_capacity: usize, // 建议 16
event_capacity: usize, // 建议 64
drop_policy: DropPolicy,
) -> Self {
let (sample_tx, sample_rx) = bounded(sample_capacity);
let (cmd_tx, cmd_rx) = bounded(cmd_capacity);
let (event_tx, event_rx) = bounded(event_capacity);
Self {
sample_tx, sample_rx,
cmd_tx, cmd_rx,
event_tx, event_rx,
dropped_samples: AtomicU64::new(0),
drop_policy,
}
}
/// 非阻塞发送样本,根据策略处理溢出
pub fn send_sample(&self, sample: FingerSample) {
match self.drop_policy {
DropPolicy::DropNewest => {
if self.sample_tx.try_send(sample).is_err() {
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
}
}
DropPolicy::DropOldest => {
if let Err(crossbeam_channel::TrySendError::Full(_)) =
self.sample_tx.try_send(sample)
{
let _ = self.sample_rx.try_recv();
if self.sample_tx.try_send(sample).is_err() {
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
}
}
}
}
}
/// 带超时的样本读取
pub fn recv_sample(&self, timeout_ms: u32) -> Result<FingerSample, SdkError> {
let timeout = std::time::Duration::from_millis(timeout_ms as u64);
self.sample_rx
.recv_timeout(timeout)
.map_err(|_| SdkError::Timeout)
}
/// 获取丢弃的样本数量
pub fn dropped_count(&self) -> u64 {
self.dropped_samples.load(Ordering::Relaxed)
}
}
```
### 10.3 通道容量配置
| 通道 | 容量 | 频率 | 说明 |
|------|------|------|------|
| 数据通道 (sample) | 64 | 低频 (~10-100Hz) | bounded传感器帧率决定 |
| 命令通道 (cmd) | 16 | 低频 (~1Hz) | bounded固定 |
| 事件通道 (event) | 64 | 低频 (~1Hz) | bounded固定 |
---
## 11. 设备管理
### 11.1 设备状态机
```
┌──────────┐ init() ┌──────────────┐
│ Idle │ ──────────────►│ Initialized │
└──────────┘ └──────┬───────┘
▲ │
│ open(port)
│ │
│ ┌────▼───────┐
│ │ Connected │
│ └────┬───────┘
│ │
│ start_stream()
│ │
│ ┌────▼───────┐
│ │ Streaming │
│ └────┬───────┘
│ │
│ stop_stream()
│ │
│ ┌────▼───────┐
│ close() │ Connected │
│◄───────────────────────└────────────┘
shutdown()
```
### 11.2 核心设备结构
```rust
// src/device.rs
pub struct EskinDeviceInner {
pub info: DeviceInfo,
pub config: DeviceConfig,
pub channels: ChannelManager,
pub state: DeviceState,
pub transport: Box<dyn SerialTransport>,
pub stream_handle: Option<JoinHandle<()>>,
pub cmd_handle: Option<JoinHandle<()>>,
pub shutdown_flag: Arc<AtomicBool>,
pub sequence_counter: AtomicU32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceState {
Idle,
Initialized,
Connected,
Streaming,
Error,
}
```
### 11.3 传输层抽象
```rust
// src/transport.rs
/// 串口传输抽象(方便测试时 mock
pub trait SerialTransport: Send + Sync {
/// 发送原始字节
fn write(&mut self, data: &[u8]) -> Result<usize, SdkError>;
/// 接收原始字节(带超时)
fn read(&mut self, buf: &mut [u8], timeout: Duration) -> Result<usize, SdkError>;
/// 清空接收缓冲区
fn flush_rx(&mut self) -> Result<(), SdkError>;
}
/// 真实串口实现
pub struct SerialPortTransport {
port: Box<dyn serialport::SerialPort>,
}
```
---
## 12. 数据采集流程
### 12.1 合力数据采集(标准模式)
```
用户调用 start_stream()
┌──────────────────────────────────────────┐
│ UART I/O Thread 启动 │
│ │
│ loop { │
│ 1. 检查 shutdown_flag │
│ 2. 检查 cmd_rx 是否有新命令 │
│ 3. 发送读请求: 0x0500, 长度 168 字节 │
│ (28 模组 × 6 字节合力) │
│ 4. 接收读应答帧 │
│ 5. 校验 CRC-8-ITU │
│ 6. 解析 28 个 CombinedForce │
│ 7. 组装 FingerSample │
│ 8. sample_tx.try_send(sample) │
│ 9. 如果配置了 read_distribution: │
│ a. 依次发送读请求获取各指分布力 │
│ b. 填充 distribution_forces │
│ } │
└──────────────────────────────────────────┘
```
### 12.2 分布力数据采集
分布力数据按模组分段读取,每段地址范围不同:
| 传感器 | 地址范围 | 每点字节数 |
|--------|---------|-----------|
| 大拇指 | `0x1000-0x11FF` | 3 (fx, fy, fz) |
| 食指 | `0x1200-0x13FF` | 3 |
| 中指 | `0x1400-0x15FF` | 3 |
| 无名指 | `0x1600-0x17FF` | 3 |
| 小拇指 | `0x1800-0x19FF` | 3 |
| 掌心 1-4 | `0x1A00-0x1BFF` | 3 |
| 掌心 5-8 | `0x1C00-0x1DFF` | 3 |
> 分布力字节数 = point_count × 3point_count 从 `0x0014-0x0018` 等寄存器读取。
### 12.3 命令处理流程
```
cmd_rx 接收到命令
match command {
StartStream → 设置 streaming 标志,开始采集循环
StopStream → 清除 streaming 标志
SetConfig(cfg) → 写入产品配置寄存器 (0x0030)
ReadRegister → 发送读请求,返回结果
WriteRegister → 发送写请求,检查状态码
Shutdown → 设置 shutdown_flag退出线程
}
```
---
## 13. C ABI 设计
### 13.1 Opaque Pointer 模式
```rust
// src/ffi/c_api.rs
/// 不透明指针C 端只能通过 API 函数操作
#[repr(C)]
pub struct EskinDevice {
inner: Box<EskinDeviceInner>,
}
/// 全局 SDK 状态
static SDK_STATE: OnceLock<Mutex<SdkState>> = OnceLock::new();
struct SdkState {
initialized: bool,
}
```
### 13.2 C API 函数签名
```c
// include/eskin_finger_sdk.h
#ifndef ESKIN_FINGER_SDK_H
#define ESKIN_FINGER_SDK_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// ─── 错误码 ──────────────────────────────────────────
typedef enum {
ESKIN_SUCCESS = 0,
ESKIN_INVALID_POINTER = 1,
ESKIN_DEVICE_NOT_FOUND = 2,
ESKIN_DEVICE_ALREADY_OPEN = 3,
ESKIN_NOT_INITIALIZED = 4,
ESKIN_ALREADY_STREAMING = 5,
ESKIN_NOT_STREAMING = 6,
ESKIN_CONFIG_ERROR = 7,
ESKIN_IO_ERROR = 8,
ESKIN_TIMEOUT = 9,
ESKIN_CHANNEL_CLOSED = 10,
ESKIN_INTERNAL_ERROR = 11,
ESKIN_BUFFER_OVERFLOW = 12,
ESKIN_INVALID_PARAMETER = 13,
ESKIN_CRC_ERROR = 14,
ESKIN_FRAME_ERROR = 15,
ESKIN_PROTOCOL_ERROR = 16,
ESKIN_DEVICE_ERROR = 17,
} EskinErrorCode;
// ─── 数据类型 ────────────────────────────────────────
typedef struct { int16_t fx, fy, fz; } EskinForce3D;
typedef struct {
uint8_t module_index; // 0-27
int16_t fx, fy, fz;
} EskinCombinedForce;
typedef struct {
uint8_t module_index;
uint16_t point_count;
EskinForce3D* points; // 长度 = point_count
} EskinDistributionForce;
typedef struct {
uint64_t timestamp_us;
uint32_t sequence;
EskinCombinedForce combined_forces[28];
EskinDistributionForce* distribution_forces; // 可选NULL 表示不采集
uint16_t distribution_count;
uint16_t module_errors[28];
} EskinSample;
typedef struct {
uint32_t serial_number;
uint16_t firmware_version;
uint16_t calibration_group;
uint16_t module_active_status;
uint16_t l_line;
uint16_t h_line;
uint32_t product_config_1;
uint32_t product_config_2;
} EskinDeviceInfo;
typedef enum {
ESKIN_DROP_NEWEST = 0,
ESKIN_DROP_OLDEST = 1,
} EskinDropPolicy;
typedef struct {
uint8_t device_addr; // 默认 0x34
int auto_transmit; // 0=不使能, 1=使能
int read_distribution; // 0=只读合力, 1=也读分布力
EskinDropPolicy drop_policy;
} EskinConfig;
// ─── 设备管理 API ────────────────────────────────────
EskinErrorCode eskin_initialize(void);
EskinErrorCode eskin_shutdown(void);
EskinErrorCode eskin_open(const char* port_name, EskinDevice** out_device);
EskinErrorCode eskin_open_by_serial(const char* serial, EskinDevice** out_device);
void eskin_close(EskinDevice* device);
// ─── 设备信息 API ────────────────────────────────────
EskinErrorCode eskin_get_device_info(EskinDevice* device, EskinDeviceInfo* out_info);
// ─── 配置 API ────────────────────────────────────────
EskinErrorCode eskin_get_config(EskinDevice* device, EskinConfig* config);
EskinErrorCode eskin_set_config(EskinDevice* device, const EskinConfig* config);
// ─── 流控制 API ──────────────────────────────────────
EskinErrorCode eskin_start_stream(EskinDevice* device);
EskinErrorCode eskin_stop_stream(EskinDevice* device);
// ─── 数据读取 API ────────────────────────────────────
EskinErrorCode eskin_read_sample(EskinDevice* device, EskinSample* out_sample,
uint32_t timeout_ms);
uint64_t eskin_get_dropped_sample_count(EskinDevice* device);
// ─── 寄存器直接访问 API ─────────────────────────────
EskinErrorCode eskin_read_register(EskinDevice* device, uint32_t addr,
uint16_t length, uint8_t* out_data);
EskinErrorCode eskin_write_register(EskinDevice* device, uint32_t addr,
uint16_t length, const uint8_t* data);
// ─── 错误管理 API ────────────────────────────────────
const char* eskin_error_string(EskinErrorCode code);
EskinErrorCode eskin_get_last_error(EskinDevice* device);
// ─── 内存管理 API ────────────────────────────────────
void eskin_free_sample(EskinSample* sample);
#ifdef __cplusplus
}
#endif
#endif // ESKIN_FINGER_SDK_H
```
---
## 14. C++ Wrapper 设计
### 14.1 RAII 封装
```cpp
// cpp/include/eskin_finger_sdk.hpp
#pragma once
#include "eskin_finger_sdk.h"
#include <functional>
#include <stdexcept>
#include <string>
#include <thread>
#include <atomic>
#include <vector>
#include <array>
namespace eskin {
// ─── 异常类型 ────────────────────────────────────────
class EskinException : public std::runtime_error {
public:
EskinException(EskinErrorCode code, const std::string& msg)
: std::runtime_error(msg), code_(code) {}
EskinErrorCode code() const { return code_; }
private:
EskinErrorCode code_;
};
// ─── 数据类型 ────────────────────────────────────────
struct Force3D { int16_t fx, fy, fz; };
struct CombinedForce {
uint8_t module_index;
int16_t fx, fy, fz;
};
struct DistributionForce {
uint8_t module_index;
std::vector<Force3D> points;
};
struct Sample {
uint64_t timestamp_us;
uint32_t sequence;
std::array<CombinedForce, 28> combined_forces;
std::vector<DistributionForce> distribution_forces;
std::array<uint16_t, 28> module_errors;
};
struct DeviceInfo {
uint32_t serial_number;
uint16_t firmware_version;
uint16_t calibration_group;
uint16_t module_active_status;
uint16_t l_line;
uint16_t h_line;
};
// ─── 回调类型 ────────────────────────────────────────
using SampleCallback = std::function<void(const Sample&)>;
using ErrorCallback = std::function<void(EskinErrorCode, const std::string&)>;
// ─── RAII Device 封装 ────────────────────────────────
class Device {
public:
Device(const Device&) = delete;
Device& operator=(const Device&) = delete;
Device(Device&& other) noexcept;
Device& operator=(Device&& other) noexcept;
~Device();
// 设备信息
DeviceInfo getDeviceInfo();
// 配置
void setConfig(const EskinConfig& config);
EskinConfig getConfig();
// 流控制
void start();
void stop();
// 同步数据读取
Sample readSample(uint32_t timeout_ms = 1000);
uint64_t getDroppedSampleCount() const;
// 寄存器直接访问
std::vector<uint8_t> readRegister(uint32_t addr, uint16_t length);
void writeRegister(uint32_t addr, const std::vector<uint8_t>& data);
// 回调模式
void setCallback(SampleCallback on_sample);
void setErrorCallback(ErrorCallback on_error);
void startAsync();
void stopAsync();
private:
friend class Sdk;
explicit Device(EskinDevice* handle);
EskinDevice* handle_ = nullptr;
SampleCallback sample_callback_;
ErrorCallback error_callback_;
std::thread callback_thread_;
std::atomic<bool> callback_running_{false};
};
// ─── SDK 全局管理 ────────────────────────────────────
class Sdk {
public:
Sdk();
~Sdk();
Sdk(const Sdk&) = delete;
Sdk& operator=(const Sdk&) = delete;
Device open(const std::string& port_name);
Device openBySerial(const std::string& serial);
static std::string errorString(EskinErrorCode code);
private:
bool initialized_ = false;
};
} // namespace eskin
```
---
## 15. 数据流示意
### 15.1 同步读取模式Pull
```
用户线程 SDK 内部
│ │
│ eskin_read_sample() │
│────────────────────────────►│ sample_rx.recv_timeout()
│ │
│ 内部循环: │
│ 1. 发送 UART 读请求 │
│ 2. 接收应答,校验 CRC │
│ 3. 解析合力/分布力 │
│ 4. 组装 FingerSample │
│ 5. 通过 channel 传递 │
│ │
│◄────────────────────────────│ 返回 FingerSample 或 Timeout
│ │
│ eskin_get_dropped_count() │
│────────────────────────────►│ atomic load
│◄────────────────────────────│ 返回 u64
```
### 15.2 回调模式Push
```
SDK 内部 用户回调
│ │
│ UART I/O 线程采集并解析 │
│ sample_tx.try_send(sample) │
│ │
│ 回调线程 recv sample │
│────────────────────────────────►│ on_sample(sample)
│ │
│ 如果采集出错 │
│────────────────────────────────►│ on_error(code, msg)
```
---
## 16. 线程模型
```
┌─────────────────────────────────────────────────────┐
│ Rust SDK 进程 │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ UART I/O Thread │ │ Command Handler │ │
│ │ (per device) │ │ (same thread) │ │
│ │ │ │ │ │
│ │ - 串口读写 │ │ - 接收 cmd_rx │ │
│ │ - CRC 校验 │ │ - 执行寄存器操作 │ │
│ │ - 协议解析 │ │ - 写入设备 │ │
│ │ - 组装样本 │ │ │ │
│ │ - sample_tx │ │ │ │
│ │ - event_tx │ │ │ │
│ └───────┬───────────┘ └───────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ Channel Manager │ │
│ │ - sample channel (bounded 64) │ │
│ │ - cmd channel (bounded 16) │ │
│ │ - event channel (bounded 64) │ │
│ │ - dropped_samples (AtomicU64) │ │
│ └───────────────────┬───────────────────┘ │
│ │ │
│ ┌───────────────────▼───────────────────┐ │
│ │ User-facing API │ │
│ │ - C ABI (extern "C") │ │
│ │ - C++ Wrapper (RAII + callback) │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
---
## 17. 关键设计决策
| 决策 | 选择 | 理由 |
|------|------|------|
| Channel 库 | `crossbeam-channel` | 比 `std::sync::mpsc` 更高性能,支持 MPMC`try_send``recv_timeout` |
| 数据通道容量 | 64 (bounded) | 传感器帧率较低 (~10-100Hz)64 帧缓冲足够 |
| 样本发送策略 | `try_send` (非阻塞) | 采集线程绝不阻塞,保证实时性 |
| 丢弃策略 | 可配置 (`DropNewest`/`DropOldest`) | 不同场景需求不同 |
| FFI 指针 | Opaque pointer (`EskinDevice*`) | 隐藏内部结构ABI 稳定 |
| 内存管理 | Box + raw pointer | Rust 侧 Box 管理C 侧只持有指针,`close()` 时归还 |
| 错误处理 | 错误码 + last_error | C ABI 用错误码C++ wrapper 转为异常 |
| 时间戳 | `u64` 微秒 | SDK 侧采集时间,避免浮点精度问题 |
| CRC 校验 | CRC-8-ITU | 协议规定,多项式 0x07初始值 0x00 |
| 传输抽象 | `SerialTransport` trait | 方便单元测试 mock可扩展支持其他传输方式 |
| 分布力采集 | 可选配置 | 分布力数据量大,按需采集避免影响帧率 |
---
## 18. 指令示例
### 18.1 读取固件版本信息
```
请求: 55 AA 09 00 34 00 FB 00 00 00 00 10 00 76
├─起始─┤├─长度─┤├地址┤├─功能─┤├─地址──┤├─长度──┤├CRC┤
应答: AA 55 09 00 34 00 FF 00 00 00 00 02 00 xx xx CRC 00
```
### 18.2 读取大拇指指尖分布力点数
```
请求: 55 AA 09 00 34 00 FB 14 00 00 00 02 00 31
起始地址 = 0x0014, 读取 2 字节
```
### 18.3 读取食指指尖合力
```
请求: 55 AA 09 00 34 00 FB 00 1C 00 00 A8 00 35
起始地址 = 0x1C00, 读取 0xA8 = 168 字节
```
### 18.4 修改食指指尖 L_LINE 值
```
写请求: 55 AA 0A 00 34 00 79 14 00 00 00 01 00 03 A4
起始地址 = 0x0014, 写入 1 字节, 数据 = 0x03
```
---
## 19. 构建方式
### Rust生成 C 共享库)
```bash
cargo build --release
# 产物: target/release/libeskin_finger_sdk.dylib (macOS)
# target/release/libeskin_finger_sdk.so (Linux)
# target/release/eskin_finger_sdk.dll (Windows)
```
### C++ WrapperCMake
```bash
mkdir build && cd build
cmake ..
make
```
### Python 绑定(未来扩展)
可以通过 `cffi``pybind11` 直接加载 C 共享库,无需额外绑定层。
---
## 20. 安全性考虑
1. **空指针检查**:所有 C API 入口检查指针非空
2. **状态检查**:操作前检查设备状态(是否已连接、是否正在采集等)
3. **CRC 校验**:每帧数据必须通过 CRC-8-ITU 校验
4. **帧完整性**:检查起始符、数据长度与实际数据一致性
5. **线程安全**:使用 `AtomicBool``AtomicU64``AtomicU32` 管理共享状态
6. **资源泄漏**C++ RAII 保证析构时释放C API 提供 `eskin_close()`
7. **panic 安全**Rust FFI 函数不 panic所有 panic 用 `catch_unwind` 捕获
8. **超时保护**:所有串口读操作带超时,避免永久阻塞