51 KiB
51 KiB
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)
[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 |
设备唯一标识符生成 | 可选 |
编译产物
crate-type = ["lib", "cdylib", "staticlib"]
lib:Rust 内部使用cdylib:生成.dylib/.so/.dll,供 C/C++ 动态链接staticlib:生成.a/.lib,供 C/C++ 静态链接
7. 数据建模
7.1 核心数据类型
// 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 设备配置
// 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 系统信息
/// 设备系统信息(从只读寄存器读取)
#[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 协议帧类型
// 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)
// 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 设备侧错误码
/// 设备应答帧中的状态码
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 校验
// 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 通道定义
// 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 核心设备结构
// 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 传输层抽象
// 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 × 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 模式
// 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 函数签名
// 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/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 共享库)
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)
mkdir build && cd build
cmake ..
make
Python 绑定(未来扩展)
可以通过 cffi 或 pybind11 直接加载 C 共享库,无需额外绑定层。
20. 安全性考虑
- 空指针检查:所有 C API 入口检查指针非空
- 状态检查:操作前检查设备状态(是否已连接、是否正在采集等)
- CRC 校验:每帧数据必须通过 CRC-8-ITU 校验
- 帧完整性:检查起始符、数据长度与实际数据一致性
- 线程安全:使用
AtomicBool、AtomicU64、AtomicU32管理共享状态 - 资源泄漏:C++ RAII 保证析构时释放;C API 提供
eskin_close() - panic 安全:Rust FFI 函数不 panic,所有 panic 用
catch_unwind捕获 - 超时保护:所有串口读操作带超时,避免永久阻塞