diff --git a/XYParser/XYEegParserExample.cpp b/XYParser/XYEegParserExample.cpp index 7dcf022..277d9bb 100644 --- a/XYParser/XYEegParserExample.cpp +++ b/XYParser/XYEegParserExample.cpp @@ -146,6 +146,20 @@ void MinimalExampleFor8ChImpedanceCommand() } } +void MinimalExampleForTriggerCommand() +{ + std::array command{}; + const int command_size = XYParser_SerializeTriggerCommand( + XYPARSER_TRIGGER_TRAIN_0, + command.data(), + command.size()); + + if (command_size == static_cast(command.size())) { + const std::uint8_t event_code = command[2]; + (void)event_code; + } +} + void MinimalExampleForImpedanceOutput() { XYParserHandle parser = XYParser_CreateParser(8); diff --git a/XYParser/XYParserApi.cpp b/XYParser/XYParserApi.cpp index 17c76bd..c993f68 100644 --- a/XYParser/XYParserApi.cpp +++ b/XYParser/XYParserApi.cpp @@ -18,6 +18,7 @@ namespace { constexpr std::uint8_t kCommandFrameHeader = 0xAA; constexpr std::uint8_t kCommandFrameTail = 0x55; constexpr std::size_t k8ChImpedanceCommandSize = 7; +constexpr std::size_t kTriggerCommandSize = 3; constexpr int k8ChLeadCount = 8; constexpr int k64ChLeadCount = 64; constexpr std::uint8_t kAlgorithmChannelCount = 64; @@ -51,6 +52,23 @@ std::array Build8ChImpedanceCommand(bool kCommandFrameTail}; } +bool IsSupportedTriggerType(std::uint8_t trigger_type) +{ + switch (trigger_type) { + case XYPARSER_TRIGGER_NONE: + case XYPARSER_TRIGGER_TRAIN_0: + case XYPARSER_TRIGGER_TRAIN_1: + return true; + default: + return false; + } +} + +std::array BuildTriggerCommand(std::uint8_t trigger_type) +{ + return {0x00, 0x00, trigger_type}; +} + constexpr std::array k8ChLeadMap = { LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7, LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8}; @@ -383,6 +401,24 @@ std::size_t XYParser_Get64ImpedanceCommandSize(void) return XYEegParser64::kImpedanceCommandSize; } +std::size_t XYParser_GetTriggerCommandSize(void) +{ + return kTriggerCommandSize; +} + +int XYParser_SerializeTriggerCommand(std::uint8_t trigger_type, + std::uint8_t* out_command, + std::size_t command_size) +{ + if (out_command == nullptr || command_size < kTriggerCommandSize || !IsSupportedTriggerType(trigger_type)) { + return 0; + } + + const auto command = BuildTriggerCommand(trigger_type); + std::copy(command.begin(), command.end(), out_command); + return static_cast(command.size()); +} + std::size_t XYParser_Get64GainSampleRateCommandSize(void) { return XYEegParser64::kGainSampleRateCommandSize; diff --git a/XYParser/XYParserApi.h b/XYParser/XYParserApi.h index 213af9b..5d6c663 100644 --- a/XYParser/XYParserApi.h +++ b/XYParser/XYParserApi.h @@ -30,6 +30,12 @@ enum XYParser8ChImpedanceSwitch : std::uint8_t { XYPARSER_8CH_IMPEDANCE_OPEN = 0xA1 }; +enum XYParserTriggerType : std::uint8_t { + XYPARSER_TRIGGER_NONE = 0x00, + XYPARSER_TRIGGER_TRAIN_0 = 0xBB, + XYPARSER_TRIGGER_TRAIN_1 = 0xBC +}; + enum XYParserLeadChannelNumber { LeadChannel_FP1 = 0, LeadChannel_FP2, @@ -213,6 +219,20 @@ XYPARSER_API int XYParser_Serialize8ChImpedanceCommand(int open, std::uint8_t* out_command, std::size_t command_size); +// Get trigger command size. +// @return Size in bytes of the trigger command. +XYPARSER_API std::size_t XYParser_GetTriggerCommandSize(void); + +// Serialize a trigger command. +// Packet layout matches WirelessEEG TriggerEventStruct: 2 reserved bytes + 1 trigger byte. +// @param trigger_type Trigger code to send. Prefer values from XYParserTriggerType. +// @param out_command Output buffer for serialized bytes. +// @param command_size Size of output buffer in bytes. +// @return Number of bytes written on success, or 0 on failure. +XYPARSER_API int XYParser_SerializeTriggerCommand(std::uint8_t trigger_type, + std::uint8_t* out_command, + std::size_t command_size); + // 获取 64 导阻抗命令的字节长度。 // @return 64 导阻抗命令的字节长度。 XYPARSER_API std::size_t XYParser_Get64ImpedanceCommandSize(void); diff --git a/XYParser/XYParserTests/Tests.cpp b/XYParser/XYParserTests/Tests.cpp index 6bb8d10..3438063 100644 --- a/XYParser/XYParserTests/Tests.cpp +++ b/XYParser/XYParserTests/Tests.cpp @@ -223,6 +223,31 @@ TEST(XYParserApiTests, Get8ChImpedanceCommandSizeMatchesSerializedLength) EXPECT_EQ(XYParser_Get8ChImpedanceCommandSize(), static_cast(7)); } +TEST(XYParserApiTests, SerializeTriggerCommandMatchesWirelessEegPacket) +{ + std::array command{}; + const int command_size = XYParser_SerializeTriggerCommand( + XYPARSER_TRIGGER_TRAIN_0, + command.data(), + command.size()); + + ASSERT_EQ(command_size, static_cast(XYParser_GetTriggerCommandSize())); + const std::array expected = {0x00, 0x00, 0xBB}; + EXPECT_TRUE(std::equal(expected.begin(), expected.end(), command.begin())); +} + +TEST(XYParserApiTests, SerializeTriggerCommandRejectsUnsupportedTriggerType) +{ + std::array command{}; + EXPECT_EQ(XYParser_SerializeTriggerCommand(0xAA, command.data(), command.size()), 0); +} + +TEST(XYParserApiTests, SerializeTriggerCommandRejectsSmallBuffer) +{ + std::array command{}; + EXPECT_EQ(XYParser_SerializeTriggerCommand(XYPARSER_TRIGGER_TRAIN_1, command.data(), command.size()), 0); +} + TEST(XYParserApiTests, GetAlgorithmDataValueCountMatchesFrameLayout) { EXPECT_EQ(XYParser_GetAlgorithmDataValueCount(), diff --git a/XYParserDataFlow.md b/XYParserDataFlow.md new file mode 100644 index 0000000..1a6d01d --- /dev/null +++ b/XYParserDataFlow.md @@ -0,0 +1,438 @@ +# XYParser 数据流与接口时序说明 + +## 1. 参与角色 + +- **64导EEG采集设备** + - 持续输出原始 EEG 字节流。 +- **上位机** + - 负责接收设备数据、调用 XYParser 库、对接算法模块。 +- **XYParser 库** + - 负责帧解析、阻抗计算、算法数据回灌后的 Welch/PSD 计算。 +- **算法** + - 接收上位机送入的算法数据,并输出用于 PSD/Welch 计算的数据。 + +## 2. 总体链路 + +当前流程可以分为两个阶段: + +- **阶段一:阻抗检测阶段** + - 设备连接后,首先下发采样率和增益配置命令。 + - 然后下发阻抗开启命令。 + - 在该阶段持续接收设备数据,并通过 `XYParser_ReadImpedance` 读取阻抗结果。 + - 阻抗检测结束后,下发阻抗关闭命令。 + +- **阶段二:常规采集与算法阶段** + - 基于 `XYParser_Feed` 解析得到的帧数据继续做常规采集。 + - 帧数据先转换为算法数据,再送入算法模块。 + - Welch/PSD 不再直接基于帧解析结果计算,而是基于算法数据,通过 `XYParser_FeedAlgorithmData` 输入后计算。 + +## 3. 接口时序图 + +### 3.1 64导初始化连接阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 64导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + + Dev-->>Host: 设备连接成功 + Host->>Lib: parser64 = XYParser_CreateParser(64) + Host->>Lib: XYParser_SetAdcParams(parser64, 4.5, 6.0) + Host->>Lib: XYParser_SetSampleRate(parser64, 250) + Host->>Lib: XYParser_SetBypassChecksum(parser64, 0) + Host->>Lib: gain_cmd_size = XYParser_Get64GainSampleRateCommandSize() + Lib-->>Host: gain_cmd_size + Host->>Lib: gain_cmd_bytes = XYParser_Serialize64GainSampleRateCommand(6, 250, gain_cmd_buf, gain_cmd_size) + Lib-->>Host: gain_cmd_bytes + Host->>Dev: 下发采样率250、增益6命令 +``` + +### 3.2 64导阻抗阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 64导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + + Host->>Lib: XYParser_SetImpedanceDetection(parser64, 1) + Host->>Lib: impedance_gain_cmd_size = XYParser_Get64GainSampleRateCommandSize() + Lib-->>Host: impedance_gain_cmd_size + Host->>Lib: impedance_gain_cmd_bytes = XYParser_Serialize64GainSampleRateCommand(24, 250, impedance_gain_cmd_buf, impedance_gain_cmd_size) + Lib-->>Host: impedance_gain_cmd_bytes + Host->>Dev: 下发采样率250、增益24命令 + Host->>Lib: impedance_cmd_size = XYParser_Get64ImpedanceCommandSize() + Lib-->>Host: impedance_cmd_size + Host->>Lib: open_impedance_bytes = XYParser_Serialize64ImpedanceCommand(1, impedance_cmd_buf, impedance_cmd_size) + Lib-->>Host: open_impedance_bytes + Host->>Dev: 下发阻抗开启命令 + + loop 持续获取阻抗 + Dev-->>Host: 原始EEG字节流 + Host->>Lib: frame_count = XYParser_Feed(parser64, raw_data, raw_size, frame_summaries, max_frames) + Lib-->>Host: frame_count + frame_summaries + Host->>Lib: impedance_count = XYParser_ReadImpedance(parser64, impedance_summaries, max_impedance) + Lib-->>Host: impedance_count + impedance_summaries + end + + Host->>Lib: close_impedance_bytes = XYParser_Serialize64ImpedanceCommand(0, impedance_cmd_buf, impedance_cmd_size) + Lib-->>Host: close_impedance_bytes + Host->>Dev: 下发阻抗关闭命令 + Host->>Lib: restore_gain_cmd_size = XYParser_Get64GainSampleRateCommandSize() + Lib-->>Host: restore_gain_cmd_size + Host->>Lib: restore_gain_cmd_bytes = XYParser_Serialize64GainSampleRateCommand(6, 250, restore_gain_cmd_buf, restore_gain_cmd_size) + Lib-->>Host: restore_gain_cmd_bytes + Host->>Dev: 下发采样率250、增益6命令 + Host->>Lib: XYParser_SetImpedanceDetection(parser64, 0) +``` + +### 3.3 64导算法阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 64导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + participant Algo as 算法 + + Host->>Lib: XYParser_SetWelchDetection(parser64, 1) + + loop 持续采集 + Dev-->>Host: 原始EEG字节流 + Host->>Lib: frame_count = XYParser_Feed(parser64, raw_data, raw_size, frame_summaries, max_frames) + Lib-->>Host: frame_count + frame_summaries + Host->>Lib: value_count = XYParser_GetAlgorithmDataValueCount() + Lib-->>Host: value_count + Host->>Lib: ok = XYParser_ConvertSampleFramesToAlgorithmData(frame_summary, algorithm_input_data) + Lib-->>Host: ok + algorithm_input_data + Host->>Algo: 输入算法数据 algorithm_input_data + Algo-->>Host: 算法输出数据 algorithm_output_bytes + Host->>Lib: alg_frame_count = XYParser_FeedAlgorithmData(parser64, algorithm_output_bytes, algorithm_output_size, algorithm_frames, max_algorithm_frames) + Lib-->>Host: alg_frame_count + algorithm_frames + Host->>Lib: welch_count = XYParser_ReadWelch(parser64, welch_summaries, max_welch) + Lib-->>Host: welch_count + welch_summaries + end + + opt 结束时处理尾数据 + Host->>Lib: flushed = XYParser_FlushAlgorithmData(parser64, tail_frame_summary) + Lib-->>Host: flushed + tail_frame_summary + Host->>Lib: welch_count = XYParser_ReadWelch(parser64, welch_summaries, max_welch) + Lib-->>Host: welch_count + welch_summaries + end + + Host->>Lib: XYParser_DestroyParser(parser64) +``` + +### 3.4 64导训练打标阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Port as 64导EEG设备串口 + participant Host as 上位机 + participant Lib as XYParser库 + participant Time as 时间 + + Host->>Lib: trigger_cmd_size = XYParser_GetTriggerCommandSize() + Lib-->>Host: trigger_cmd_size + Host->>Lib: train0_bytes = XYParser_SerializeTriggerCommand(XYPARSER_TRIGGER_TRAIN_0, trigger_cmd_buf, trigger_cmd_size) + Lib-->>Host: train0_bytes + Host->>Port: 发送 TRAIN_0 打标 + Host->>Time: 开始训练计时 + Time-->>Host: 训练时间到 + Host->>Lib: train1_bytes = XYParser_SerializeTriggerCommand(XYPARSER_TRIGGER_TRAIN_1, trigger_cmd_buf, trigger_cmd_size) + Lib-->>Host: train1_bytes + Host->>Port: 发送 TRAIN_1 打标 +``` + +### 3.5 8导初始化连接阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 8导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + + Dev-->>Host: 设备连接成功 + Host->>Lib: parser8 = XYParser_CreateParser(8) + Host->>Lib: XYParser_SetAdcParams(parser8, vref, gain) + Host->>Lib: XYParser_SetSampleRate(parser8, sample_rate) + Host->>Lib: XYParser_SetBypassChecksum(parser8, 0) +``` + +### 3.6 8导阻抗阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 8导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + + Host->>Lib: XYParser_SetImpedanceDetection(parser8, 1) + Host->>Lib: impedance_cmd_size = XYParser_Get8ChImpedanceCommandSize() + Lib-->>Host: impedance_cmd_size + Host->>Lib: open_impedance_bytes = XYParser_Serialize8ChImpedanceCommand(1, impedance_cmd_buf, impedance_cmd_size) + Lib-->>Host: open_impedance_bytes + Host->>Dev: 下发阻抗开启命令 + + loop 持续获取阻抗 + Dev-->>Host: 原始EEG字节流 + Host->>Lib: frame_count = XYParser_Feed(parser8, raw_data, raw_size, frame8_summaries, max_frames) + Lib-->>Host: frame_count + frame8_summaries + Host->>Lib: impedance_count = XYParser_ReadImpedance(parser8, impedance_summaries, max_impedance) + Lib-->>Host: impedance_count + impedance_summaries + end + + Host->>Lib: close_impedance_bytes = XYParser_Serialize8ChImpedanceCommand(0, impedance_cmd_buf, impedance_cmd_size) + Lib-->>Host: close_impedance_bytes + Host->>Dev: 下发阻抗关闭命令 + Host->>Lib: XYParser_SetImpedanceDetection(parser8, 0) +``` + +### 3.7 8导算法阶段 + +```mermaid +%%{init: {'theme': 'default', 'sequence': {'diagramMarginX': 80, 'diagramMarginY': 30, 'actorMargin': 80, 'width': 220, 'height': 80, 'messageMargin': 35}}}%% +sequenceDiagram + participant Dev as 8导EEG采集设备 + participant Host as 上位机 + participant Lib as XYParser库 + participant Algo as 算法 + + Host->>Lib: XYParser_SetWelchDetection(parser8, 1) + + loop 持续采集 + Dev-->>Host: 原始EEG字节流 + Host->>Lib: frame_count = XYParser_Feed(parser8, raw_data, raw_size, frame8_summaries, max_frames) + Lib-->>Host: frame_count + frame8_summaries + Host->>Lib: converted = XYParser_Convert8ChFramesTo64Ch(frame8_summary, 1, frame64_summary, 1) + Lib-->>Host: converted + frame64_summary + Host->>Lib: value_count = XYParser_GetAlgorithmDataValueCount() + Lib-->>Host: value_count + Host->>Lib: ok = XYParser_ConvertSampleFramesToAlgorithmData(frame64_summary, algorithm_input_data) + Lib-->>Host: ok + algorithm_input_data + Host->>Algo: 输入算法数据 algorithm_input_data + Algo-->>Host: 算法输出数据 algorithm_output_bytes + Host->>Lib: alg_frame_count = XYParser_FeedAlgorithmData(parser8, algorithm_output_bytes, algorithm_output_size, algorithm_frames, max_algorithm_frames) + Lib-->>Host: alg_frame_count + algorithm_frames + Host->>Lib: welch_count = XYParser_ReadWelch(parser8, welch_summaries, max_welch) + Lib-->>Host: welch_count + welch_summaries + end + + opt 结束时处理尾数据 + Host->>Lib: flushed = XYParser_FlushAlgorithmData(parser8, tail_frame_summary) + Lib-->>Host: flushed + tail_frame_summary + Host->>Lib: welch_count = XYParser_ReadWelch(parser8, welch_summaries, max_welch) + Lib-->>Host: welch_count + welch_summaries + end + + Host->>Lib: XYParser_DestroyParser(parser8) +``` + +## 4. 关键接口职责 + +### 4.1 设备参数配置 + +- `XYParser_SetAdcParams` + - 设置库内使用的 ADC 参考电压和增益参数。 + - 该参数影响原始采样值到微伏值的换算。 + +- `XYParser_SetSampleRate` + - 设置库内处理逻辑使用的采样率。 + - 该参数影响阻抗、Welch/PSD 等后续处理。 + +- `XYParser_Get64GainSampleRateCommandSize` + - 获取 64 导设备增益和采样率配置命令所需的缓冲区大小。 + +- `XYParser_Serialize64GainSampleRateCommand` + - 根据目标增益和采样率生成下发给 64 导设备的命令字节流。 + - 上位机拿到该命令后发送给 EEG 设备,使设备端采样参数与库内配置保持一致。 + - 当前推荐流程中: + - `vref` 固定为 `4.5`。 + - 设备连接成功后,先下发 `250Hz + 增益6`。 + - 开启阻抗前,下发 `250Hz + 增益24`,同时调用 `XYParser_SetImpedanceDetection(handle, 1)`,使库内 gain 自动切到 `24`。 + - 关闭阻抗后,再下发 `250Hz + 增益6`,同时调用 `XYParser_SetImpedanceDetection(handle, 0)`,使库内 gain 自动恢复到 `6`。 + +- `XYParser_Get64ImpedanceCommandSize` + - 获取 64 导阻抗开关命令所需的缓冲区大小。 + +- `XYParser_Serialize64ImpedanceCommand` + - 生成下发给 64 导设备的阻抗开关命令字节流。 + - `open = 1` 表示开启阻抗检测,`open = 0` 表示关闭阻抗检测。 + +- `XYParser_Get8ChImpedanceCommandSize` + - 获取 8 导阻抗开关命令所需的缓冲区大小。 + +- `XYParser_Serialize8ChImpedanceCommand` + - 生成下发给 8 导设备的阻抗开关命令字节流。 + - `open = 1` 表示开启阻抗检测,`open = 0` 表示关闭阻抗检测。 + - 8 导流程中,设备连接后**不需要**额外下发增益和采样率命令。 + +### 4.2 原始数据解析 + +- `XYParser_Feed` + - 输入设备原始字节流。 + - 输出解析后的 `XYParserFrameSummary` 数组。 + - 该接口当前仍负责驱动阻抗相关计算。 + - 该接口当前**不再驱动 Welch/PSD 计算**。 + +### 4.3 阻抗读取 + +- `XYParser_SetImpedanceDetection` + - 控制是否启用阻抗检测。 + - 启用时,库内 ADC 增益自动切换到 `24`。 + - 关闭时,库内 ADC 增益自动恢复到 `6`。 + - 该接口只修改库内解析参数,不会自动给设备发送控制命令。 + +- `XYParser_ReadImpedance` + - 读取当前已经累计完成的阻抗结果。 + - 阻抗结果来源于阻抗检测阶段的帧解析链路。 + +### 4.4 帧转算法数据 + +- `XYParser_ConvertSampleFramesToAlgorithmData` + - 将单帧 `XYParserFrameSummary` 转为算法需要的连续数组。 + - 上位机通常在拿到帧数据后调用此接口,再把结果送入算法模块。 + +- `XYParser_Convert8ChFramesTo64Ch` + - 将 8 导帧转换为 64 导帧,未映射导联补 0。 + - 8 导流程中,在送算法数据前,需要先把 8 导帧转换为 64 导帧,再调用 `XYParser_ConvertSampleFramesToAlgorithmData`。 + +### 4.5 算法数据回灌 + +- `XYParser_FeedAlgorithmData` + - 输入算法数据字节流。 + - 内部先按采样缓存,再按每 5 个采样组装为一帧。 + - 同时驱动 Welch/PSD 计算。 + - 可选输出重新组装后的 `XYParserFrameSummary`。 + +- `XYParser_ResetAlgorithmDataCache` + - 清空算法数据缓存。 + - 适合在切换任务、重置状态时调用。 + +- `XYParser_FlushAlgorithmData` + - 将缓存中不足 5 个采样的尾数据补齐为 1 帧输出。 + - 用于结束阶段处理残留数据。 + +### 4.6 Welch/PSD 读取 + +- `XYParser_SetWelchDetection` + - 控制是否启用基于算法数据的 Welch 检测。 + +- `XYParser_ReadWelch` + - 读取当前已累计完成的 Welch/PSD 结果。 + - Welch 结果当前仅来源于 `XYParser_FeedAlgorithmData`。 + +## 5. 当前设计结论 + +### 5.1 阻抗数据来源 + +- 阻抗在独立的阻抗检测阶段获取。 +- 即: + - 设备连接成功 + - 下发采样率250和增益6命令 + - `XYParser_SetImpedanceDetection(handle, 1)` + - 下发采样率250和增益24命令 + - 下发阻抗开启命令 + - 设备原始字节流 + - `XYParser_Feed` + - `XYParser_ReadImpedance` + - `XYParser_SetImpedanceDetection(handle, 0)` + - 下发阻抗关闭命令 + - 下发采样率250和增益6命令 + +### 5.2 PSD 数据来源 + +- PSD/Welch 不再直接使用 `XYParser_Feed` 解析出来的帧数据。 +- 当前流程为: + - 设备原始字节流 + - `XYParser_Feed` + - `XYParser_ConvertSampleFramesToAlgorithmData` + - 算法处理 + - `XYParser_FeedAlgorithmData` + - `XYParser_ReadWelch` + +### 5.3 8导算法数据来源 + +- 8 导流程中,算法输入和 Welch/PSD 仍然按 64 导数据格式处理。 +- 当前流程为: + - 8 导设备原始字节流 + - `XYParser_Feed` + - `XYParser_Convert8ChFramesTo64Ch` + - `XYParser_ConvertSampleFramesToAlgorithmData` + - 算法处理 + - `XYParser_FeedAlgorithmData` + - `XYParser_ReadWelch` + +## 6. 推荐调用顺序 + +### 6.1 64导推荐调用顺序 + +```text +1. EEG 设备连接成功 +2. CreateParser +3. SetAdcParams(4.5, 6) / SetSampleRate(250) / SetBypassChecksum +4. Get64GainSampleRateCommandSize / Serialize64GainSampleRateCommand(6, 250) +5. 上位机向设备下发采样率250、增益6命令 +6. SetImpedanceDetection(1) +7. Get64GainSampleRateCommandSize / Serialize64GainSampleRateCommand(24, 250) +8. 上位机向设备下发采样率250、增益24命令 +9. Get64ImpedanceCommandSize / Serialize64ImpedanceCommand(1) +10. 上位机向设备下发阻抗开启命令 +11. 阻抗检测阶段循环: + 11.1 Feed 原始字节流,拿到帧 + 11.2 ReadImpedance 读取阻抗 +12. Serialize64ImpedanceCommand(0) +13. 上位机向设备下发阻抗关闭命令 +14. SetImpedanceDetection(0) +15. Get64GainSampleRateCommandSize / Serialize64GainSampleRateCommand(6, 250) +16. 上位机向设备下发采样率250、增益6命令 +17. SetWelchDetection +18. 常规采集阶段循环: + 18.1 Feed 原始字节流,拿到帧 + 18.2 将帧转换为算法数据 + 18.3 将算法数据送入算法模块 + 18.4 将算法输出数据通过 FeedAlgorithmData 回灌 + 18.5 ReadWelch 读取 PSD/Welch 结果 +19. 必要时 FlushAlgorithmData +20. DestroyParser +``` + +### 6.2 8导推荐调用顺序 + +```text +1. EEG 设备连接成功 +2. CreateParser +3. SetAdcParams(vref, gain) / SetSampleRate(sample_rate) / SetBypassChecksum +4. SetImpedanceDetection(1) +5. Get8ChImpedanceCommandSize / Serialize8ChImpedanceCommand(1) +6. 上位机向设备下发阻抗开启命令 +7. 阻抗检测阶段循环: + 7.1 Feed 原始字节流,拿到8导帧 + 7.2 ReadImpedance 读取阻抗 +8. Serialize8ChImpedanceCommand(0) +9. 上位机向设备下发阻抗关闭命令 +10. SetImpedanceDetection(0) +11. SetWelchDetection +12. 常规采集阶段循环: + 12.1 Feed 原始字节流,拿到8导帧 + 12.2 将8导帧转换为64导帧 + 12.3 将64导帧转换为算法数据 + 12.4 将算法数据送入算法模块 + 12.5 将算法输出数据通过 FeedAlgorithmData 回灌 + 12.6 ReadWelch 读取 PSD/Welch 结果 +13. 必要时 FlushAlgorithmData +14. DestroyParser +``` + +## 7. 一句话总结 + +- **阻抗是独立阶段,先开阻抗、持续读取、再关阻抗。** +- **Welch/PSD 走算法数据链路。** +- **8导送算法前,先转成64导数据。**