# 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, } /// 单个传感器模组的合力数据 #[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, /// 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, } /// 读应答帧结构 pub struct ReadResponse { pub device_addr: u8, pub start_addr: u32, pub data: Vec, 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 { 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 }, 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, pub sample_rx: Receiver, // 命令通道 — 控制命令 pub cmd_tx: Sender, pub cmd_rx: Receiver, // 事件通道 — 事件通知 pub event_tx: Sender, pub event_rx: Receiver, // 丢弃计数器 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 { 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, pub stream_handle: Option>, pub cmd_handle: Option>, pub shutdown_flag: Arc, 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; /// 接收原始字节(带超时) fn read(&mut self, buf: &mut [u8], timeout: Duration) -> Result; /// 清空接收缓冲区 fn flush_rx(&mut self) -> Result<(), SdkError>; } /// 真实串口实现 pub struct SerialPortTransport { port: Box, } ``` --- ## 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 × 3,point_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, } /// 全局 SDK 状态 static SDK_STATE: OnceLock> = 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 #include #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 #include #include #include #include #include #include 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 points; }; struct Sample { uint64_t timestamp_us; uint32_t sequence; std::array combined_forces; std::vector distribution_forces; std::array 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; using ErrorCallback = std::function; // ─── 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 readRegister(uint32_t addr, uint16_t length); void writeRegister(uint32_t addr, const std::vector& 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 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++ Wrapper(CMake) ```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. **超时保护**:所有串口读操作带超时,避免永久阻塞