diff --git a/.gitignore b/.gitignore index a98648c..04db570 100644 --- a/.gitignore +++ b/.gitignore @@ -238,5 +238,4 @@ install_manifest.txt compile_commands.json CTestTestfile.cmake _deps -CMakeUserPresets.json - +CMakeUserPresets.json \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..18b0e9c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,444 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "eskin-finger-sdk" +version = "0.1.0" +dependencies = [ + "chrono", + "crossbeam-channel", + "fern", + "libc", + "log", + "serde", + "serde_json", + "thiserror", + "uuid", +] + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "log", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d7abeaf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "eskin-finger-sdk" +version = "0.1.0" +edition = "2024" + +[lib] +name = "eskin_finger_sdk" +crate-type = ["lib", "cdylib", "staticlib"] + +[dependencies] +chrono = "0.4.44" +crossbeam-channel = "0.5.15" +fern = "0.7.1" +libc = "0.2.186" +log = "0.4.29" +serde = "1.0.228" +serde_json = "1.0.149" +thiserror = "2.0.18" +uuid = "1.23.1" diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..e5f41e2 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,1429 @@ +# 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. **超时保护**:所有串口读操作带超时,避免永久阻塞 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..dd198c6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod types; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..e69de29