310 lines
18 KiB
Markdown
310 lines
18 KiB
Markdown
|
|
# PCIe8586M 采集调试程序 — 开发文档
|
|||
|
|
|
|||
|
|
> 给本机 Claude Code 的工作指令。本程序是「项目接入前测试探针」,目标是摸清阿尔泰 PCIe8586M 高速数字化仪的驱动行为,为主项目(.NET 8 + WPF + MVVM)提供经过验证的接入方案与精确配置。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 0. 给 Claude Code 的首要约定
|
|||
|
|
|
|||
|
|
1. **函数名/结构体/常量的唯一权威是 `Samples/` 目录下的 VC 示例和 `Samples/VC/Include/*.h` 头文件。** 本文档刻意不写死任何 SDK 函数名。凡涉及具体 API,先 `grep`/阅读 `Samples/VC/Include` 与 `Samples/VC/Simple/AD/Continue`、`Finite`,以头文件为准。
|
|||
|
|
2. **Samples 是 VC(C/C++)的。** C# 接入靠 P/Invoke:把 `.h` 的 `extern "C"` 函数翻译成 `[DllImport]`,把 C 结构体翻译成 `[StructLayout(LayoutKind.Sequential)]`。翻译方法论见 §6。
|
|||
|
|
3. **分层不可破坏。** 只有 `Hardware/Pcie8586Digitizer.cs` 一个文件允许出现 `[DllImport]` 和 SDK 类型。其余所有代码只依赖 `IDigitizer` 接口。这样主项目可原样复用 `Hardware` + `Acquisition` 两层。
|
|||
|
|
4. **先用模拟卡跑通全链路,再接真卡。** 本机无硬件、无驱动,开发阶段用 `SimulatedDigitizer` 验证 UI/数据流/存盘;真卡实现 `Pcie8586Digitizer` 时只改这一层。
|
|||
|
|
5. **平台必须 x64。** SDK 是 64 位非托管 DLL,工程 `<PlatformTarget>x64</PlatformTarget>`,否则运行时 `BadImageFormatException`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 硬件事实(已从手册固化,视为常量,不要再"识别")
|
|||
|
|
|
|||
|
|
设备:`ACTS1000 PCIE8586 8CH 16Bit 100Msps High Speed Digitizer`
|
|||
|
|
硬件 ID:`PCI\VEN_1E42&DEV_8586`(厂商号 0x1E42 = 阿尔泰,设备号 0x8586)
|
|||
|
|
驱动版本:1.6.2.0(2025/7/20)
|
|||
|
|
|
|||
|
|
| 项目 | 值 | 来源 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 通道数 | 8(单端) | 手册 2.3.2 |
|
|||
|
|
| ADC 分辨率 | **16 bit**(8586M) | 手册 2.2 |
|
|||
|
|
| 最高采样率 | 100 MS/s | 手册 2.1 |
|
|||
|
|
| 输入量程 | ±5V 或 ±1V(软件可选) | 手册 2.3.2 |
|
|||
|
|
| 板载内存 | 2 GB(八通道共享) | 手册 2.3.6 |
|
|||
|
|
| 数据传输 | DMA | 手册 2.3.6 |
|
|||
|
|
| 总线 | PCIe 2.0 x8 | 手册 2.3.1 |
|
|||
|
|
| 支持系统 | XP/Win7/Win8/Win10 | 手册 2.3.1 |
|
|||
|
|
|
|||
|
|
### 1.1 数据格式(关键,§4 解析的依据)
|
|||
|
|
|
|||
|
|
- 每个采样点 = **16 bit 无符号原始码值 = 2 字节,小端**。
|
|||
|
|
- **码值→电压换算公式(仅 8586M,手册 4.3)**,单位 mV:
|
|||
|
|
- ±5V 量程:`Volt = (10000.0 / 65536) * (code & 0xFFFF) - 5000.0`
|
|||
|
|
- ±1V 量程:`Volt = (2000.0 / 65536) * (code & 0xFFFF) - 1000.0`
|
|||
|
|
- 注意 8582M(12bit)、8584M(14bit) 公式不同(分母 4096/16384),**本卡是 16bit,分母固定 65536**。
|
|||
|
|
- 中间值(零点)码值 = 0x8000 = 32768,对应 0V。
|
|||
|
|
|
|||
|
|
### 1.2 多通道数据排列(解交错依据,手册 4.6)
|
|||
|
|
|
|||
|
|
- 使能通道数只能是 **1 / 2 / 4 / 8**,且必须**从 CH0 起连续**(4 通道 = CH0~3;单通道只能是 CH0)。
|
|||
|
|
- 多通道时按**采样点交错**存放(每点 1 个字):
|
|||
|
|
```
|
|||
|
|
CH0[0] CH1[0] CH2[0] CH3[0] CH4[0] CH5[0] CH6[0] CH7[0]
|
|||
|
|
CH0[1] CH1[1] CH2[1] CH3[1] CH4[1] CH5[1] CH6[1] CH7[1]
|
|||
|
|
...
|
|||
|
|
```
|
|||
|
|
- 解交错:`result[ch][s] = raw[s * channelCount + ch]`。
|
|||
|
|
|
|||
|
|
### 1.3 采样率是离散值(采集频率控制依据,手册 4.7)
|
|||
|
|
|
|||
|
|
- `采样率 = 扫描时基频率 / ADC时钟分频器`
|
|||
|
|
- 时基 = 100 MHz;分频器 = 1, 2, 3, ... (2³²−1) 的**正整数**。
|
|||
|
|
- 所以可设采样率 = `100MHz / N`:100M、50M、33.33M、25M ... 1M(N=100) ... 1k(N=100000)。
|
|||
|
|
- **UI 不能让用户自由填任意采样率。** 应让用户选分频器 N,或从预设档(100M/50M/10M/1M/100k...)选,并实时显示 `100e6/N` 的实际采样率。
|
|||
|
|
|
|||
|
|
### 1.4 采集模式与触发(手册 4.8 / 4.9)
|
|||
|
|
|
|||
|
|
- 模式:**连续采样**、**有限点采样**。无硬件级"暂停/继续"。
|
|||
|
|
- 触发源:软件触发、ATR(模拟)、DTR(数字 PFI)、同步信号。**调试程序默认软件触发**。
|
|||
|
|
- 有限点支持:中间触发、后触发、预触发、硬件延时触发、重复触发。
|
|||
|
|
- **定时录制(本程序核心功能)映射**:见 §3.2。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 架构
|
|||
|
|
|
|||
|
|
三层,自底向上:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Hardware 层 IDigitizer 接口 + Pcie8586Digitizer(真卡, 唯一含 DllImport)
|
|||
|
|
+ SimulatedDigitizer(模拟卡)
|
|||
|
|
↓
|
|||
|
|
Acquisition 层 后台采集线程 / 环形历史缓冲 / 解交错 / 码值换算 / 定时录制 / 存盘
|
|||
|
|
↓
|
|||
|
|
UI 层 (WPF+MVVM) 设备识别 / 实时曲线(ScottPlot) / 实时表格 / 录制控制 / 剪辑
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**复用边界**:`Hardware` + `Acquisition` 两层将原样进主项目;UI 层主项目重写。因此这两层**不得引用任何 WPF 类型**(无 `Dispatcher`、无 `ObservableCollection`),保持纯 .NET。
|
|||
|
|
|
|||
|
|
### 2.1 建议目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Pcie8586Probe/
|
|||
|
|
├─ Pcie8586Probe.csproj # net8.0-windows, UseWPF, x64
|
|||
|
|
├─ Samples/ # ← 你放上位机参考的地方(VC 示例 + Include 头文件)
|
|||
|
|
├─ Hardware/
|
|||
|
|
│ ├─ IDigitizer.cs # 抽象接口(见 §5.1 职责)
|
|||
|
|
│ ├─ SimulatedDigitizer.cs # 模拟实现(生成交错原始码值)
|
|||
|
|
│ └─ Pcie8586Digitizer.cs # 真卡实现(P/Invoke,照 Samples 填)
|
|||
|
|
├─ Acquisition/
|
|||
|
|
│ ├─ CodeConverter.cs # 码值→电压 + 解交错(§1.1/§1.2)
|
|||
|
|
│ ├─ RingHistoryBuffer.cs # 录制/剪辑用的最近 N 秒环形缓冲
|
|||
|
|
│ ├─ Recorder.cs # 定时定长录制状态机(§3.2)
|
|||
|
|
│ └─ WaveformWriter.cs # 落盘(二进制 + JSON 头)
|
|||
|
|
├─ Models/
|
|||
|
|
│ ├─ AcquisitionConfig.cs # 通道数/量程/分频器/模式 等配置
|
|||
|
|
│ ├─ DeviceInfo.cs
|
|||
|
|
│ └─ SampleBlock.cs # 一块已解交错的电压数据
|
|||
|
|
├─ ViewModels/
|
|||
|
|
│ ├─ MainViewModel.cs
|
|||
|
|
│ └─ ChannelRow.cs # 表格一行(通道实时统计)
|
|||
|
|
├─ Views/
|
|||
|
|
│ └─ MainWindow.xaml(.cs)
|
|||
|
|
└─ Converters/ # XAML 值转换器(如枚举→显示文本)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 NuGet 依赖
|
|||
|
|
|
|||
|
|
- `CommunityToolkit.Mvvm`(`[ObservableProperty]` / `[RelayCommand]` 源生成器,减样板)。
|
|||
|
|
- `ScottPlot.WPF`(高刷新实时曲线,远优于 WPF 原生绘图)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 功能需求与验收点
|
|||
|
|
|
|||
|
|
### 3.1 设备识别(自动 + 手动)
|
|||
|
|
|
|||
|
|
- **自动**:调用 SDK 枚举接口列出所有同型号板卡,填充下拉框(显示逻辑号 + 描述)。
|
|||
|
|
- 多数阿尔泰 SDK 有 `GetDeviceCount` 之类;若没有,可循环 `CreateDevice(i)` 试探,成功记录后 `ReleaseDevice`。**具体函数看 Samples/VC/Simple/AD/PhysicalID 或 Include。**
|
|||
|
|
- **手动**:用户直接填逻辑号(0/1/2...)打开。
|
|||
|
|
- 验收:模拟卡返回 1 张虚拟卡可被选中打开;真卡能枚举到 `VEN_1E42&DEV_8586`。
|
|||
|
|
|
|||
|
|
### 3.2 定时定长录制 +剪辑(替代"暂停/继续")
|
|||
|
|
|
|||
|
|
这是本程序与普通示波器软件的核心差异点。语义:
|
|||
|
|
|
|||
|
|
- **连续采集常驻**:打开设备并 Start 后,DMA 持续流入,曲线实时滚动显示。这一路**不落盘**,只进 `RingHistoryBuffer`(保留最近 N 秒,N 可配,受内存约束见 §7)。
|
|||
|
|
- **定时录制按钮**:1ms / 10ms / 100ms / 1s / 10s 五个预设。点击后:
|
|||
|
|
- 计算目标点数 `target = round(采样率 × 时长)`。
|
|||
|
|
- 进入"录制中"状态:从下一个数据块起,把块追加进录制缓冲,累计每通道点数 ≥ target 时**自动停止录制**,截断到精确 target 点,落盘。
|
|||
|
|
- 录制中曲线继续显示、不影响连续监控。
|
|||
|
|
- **采集后剪辑**:录制完(或从历史缓冲)得到一段数据后,提供"起点/终点"两个游标(UI 上拖动或填采样序号),裁出子区间另存。
|
|||
|
|
- 验收:选 1MS/s、录 10ms → 落盘文件每通道恰好 10000 点(±0,因软件截断);裁剪 [2000,5000) → 3000 点。
|
|||
|
|
|
|||
|
|
> **两种实现取舍(文档建议默认前者)**:
|
|||
|
|
> 1. **连续采集 + 软件截取**(默认):录制只是从连续流里数够点数。能边显示边录,长度精确(软件截断),两段录制无硬件重启间隙。
|
|||
|
|
> 2. **有限点 + 软件触发**(备选):每次录制重新 init→start,硬件采满 target 点自停。最干净,但与实时显示互斥、两段间有重配置延时。
|
|||
|
|
> 真卡阶段若连续采集 DMA 吞吐顶不住(§7),再退到方案 2。
|
|||
|
|
|
|||
|
|
### 3.3 实时显示
|
|||
|
|
|
|||
|
|
- **曲线**:ScottPlot,每通道一条线,X 轴时间(ms 或样本序号),Y 轴电压(V),量程决定 Y 范围(±5 或 ±1)。只画最近一屏的点(降采样显示,见 §7)。
|
|||
|
|
- **表格**:每通道一行,列 = 通道名 / 最新值 / Min / Max / RMS,随采集刷新(节流到 ~10–20 Hz)。
|
|||
|
|
|
|||
|
|
### 3.4 采集参数控制
|
|||
|
|
|
|||
|
|
- 通道数下拉:1/2/4/8。
|
|||
|
|
- 量程下拉:±5V / ±1V。
|
|||
|
|
- 采样率:选分频器 N 或预设档,旁显实际 `100e6/N`。
|
|||
|
|
- 模式:连续 / 有限点(有限点需填点数)。
|
|||
|
|
|
|||
|
|
### 3.5 存盘格式(见 §8,与未来 Python 分析对齐)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 数据解析(Acquisition/CodeConverter.cs)
|
|||
|
|
|
|||
|
|
实现两个纯函数(无副作用、无 SDK 依赖、可单测):
|
|||
|
|
|
|||
|
|
1. `CodeToVolts(ushort code, InputRange range)` → 按 §1.1 公式返回 V。
|
|||
|
|
2. `Deinterleave(ReadOnlySpan<ushort> raw, int channelCount, InputRange range)` → `double[][]`([通道][点]),按 §1.2 排列解交错并换算。
|
|||
|
|
|
|||
|
|
单元测试验收:`code=0x8000` → 0V;`code=0xFFFF` → +5V 量程下约 +4.99985V;`code=0` → −5V。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 各层职责(不写函数名,描述 API 类别)
|
|||
|
|
|
|||
|
|
### 5.1 IDigitizer 接口应暴露的能力
|
|||
|
|
|
|||
|
|
- 枚举设备 → 返回 `IReadOnlyList<DeviceInfo>`。
|
|||
|
|
- 按逻辑号打开 / 关闭。
|
|||
|
|
- 用 `AcquisitionConfig` 初始化采集(设量程、通道掩码、时钟分频、模式、软件触发)。
|
|||
|
|
- 启动采集:以**回调或 IAsyncEnumerable** 的方式持续吐出 `SampleBlock`(已解交错、已换算),直到取消或有限点采满。
|
|||
|
|
- 实现 `IDisposable` 释放 SDK 资源。
|
|||
|
|
|
|||
|
|
### 5.2 Pcie8586Digitizer(真卡,唯一含 P/Invoke)
|
|||
|
|
|
|||
|
|
照 `Samples/VC/Simple/AD/Continue` 的调用顺序翻译。**典型**连续采集流程(具体函数名看 Samples):
|
|||
|
|
|
|||
|
|
1. **创建设备**:`CreateDevice(逻辑号)` → 返回设备句柄(HANDLE/IntPtr)。判断无效值(通常 0 或 -1/INVALID_HANDLE_VALUE)。
|
|||
|
|
2. **配置参数**:填充 SDK 的"AD 参数结构体"(量程枚举、通道数/掩码、`ADC时钟分频器 = config.ClockDivider`、采集模式、触发源=软件触发、是否主卡等),调用初始化函数(`InitDeviceAD` / `SetADParam` 一类)。
|
|||
|
|
3. **启动**:`StartDeviceAD(句柄)`。
|
|||
|
|
4. **读 DMA**:循环调用读取函数(`GetDeviceAD` / `ReadDeviceAD`,参数通常是 `句柄, 缓冲区指针, 期望点数, 超时ms`),返回实际读到的点数。把读到的 `ushort[]` 原始码值交给 `CodeConverter.Deinterleave` → `SampleBlock` → 回调。
|
|||
|
|
5. **软件触发**:若初始化为软件触发,需在 Start 后调用一次软件触发函数(手册 4.8.2)。
|
|||
|
|
6. **停止/释放**:`StopDeviceAD(句柄)` → `ReleaseDevice(句柄)`。
|
|||
|
|
|
|||
|
|
读取循环必须在**后台线程/Task**,不可阻塞 UI 线程。缓冲区用 `GCHandle.Alloc(buf, GCHandleType.Pinned)` 固定后传 `AddrOfPinnedObject()`,用完 `Free()`;或用 `Marshal.AllocHGlobal` + `Marshal.Copy`。
|
|||
|
|
|
|||
|
|
### 5.3 SimulatedDigitizer(模拟卡)
|
|||
|
|
|
|||
|
|
- 不碰 SDK。内部按 §1.2 布局生成**交错的 ushort 原始码值**(每通道不同频率正弦 + 1% 噪声),再走与真卡**完全相同**的 `Deinterleave` 路径,确保两条路径解析逻辑一致。
|
|||
|
|
- 按配置的采样率节流出块(建议每秒约 30 块,每块点数 = 采样率/30 上限封顶几千点),`await Task.Delay` 模拟 DMA 节奏。
|
|||
|
|
- 支持连续与有限点(有限点采满点数后结束)。
|
|||
|
|
|
|||
|
|
### 5.4 MainViewModel(UI 线程安全是重点)
|
|||
|
|
|
|||
|
|
- 用 `CommunityToolkit.Mvvm` 的 `[ObservableProperty]`/`[RelayCommand]`。
|
|||
|
|
- **高速数据进 UI 的关键**:采集回调在后台线程触发,**不要**每块都 `Dispatcher.Invoke` 更新绑定属性(会卡死 UI)。正确做法:
|
|||
|
|
- 后台把最新数据写入一个**无锁/带锁的"最新帧"缓冲**;
|
|||
|
|
- 用一个 `DispatcherTimer`(~50ms,即 20Hz)在 UI 线程定时把最新帧刷到 ScottPlot 和表格。
|
|||
|
|
- 即"采集高频、渲染定频",解耦两者速率。
|
|||
|
|
- 命令:`Open/Close/StartMonitor/StopMonitor/Record(时长)/ClearScreen/SaveClip(起,止)`。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. P/Invoke 翻译方法论(VC 头文件 → C#)
|
|||
|
|
|
|||
|
|
Claude Code 对着 `Include/*.h` 按下表翻译。**逐个核对头文件的真实类型,不要套用记忆。**
|
|||
|
|
|
|||
|
|
| C / VC 头文件 | C# P/Invoke | 备注 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `HANDLE` / `void*` / `DEVICE_HANDLE` | `IntPtr` | 设备句柄一律 IntPtr |
|
|||
|
|
| `int` / `LONG` | `int` | 4 字节 |
|
|||
|
|
| `unsigned int` / `DWORD` / `ULONG` | `uint` | 时钟分频器常是 DWORD |
|
|||
|
|
| `unsigned short` / `WORD` / `USHORT` | `ushort` | 原始码值类型 |
|
|||
|
|
| `unsigned char` / `BYTE` | `byte` | |
|
|||
|
|
| `BOOL` | `[return: MarshalAs(UnmanagedType.Bool)] ... bool` 或直接 `int` | BOOL 是 4 字节 int,别用 1 字节 bool |
|
|||
|
|
| `char*`(入参字符串) | `string` + `CharSet` / 或 `byte[]` | 看是 ANSI 还是宽字符 |
|
|||
|
|
| `WORD* pData` / `void* pBuffer`(缓冲区) | `IntPtr` + pinning,或 `ushort[]` | 大缓冲建议 IntPtr + Marshal.Copy |
|
|||
|
|
| `struct { ... }`(参数结构体) | `[StructLayout(LayoutKind.Sequential)] struct` | **字段顺序、类型、对齐必须 1:1** |
|
|||
|
|
| `enum` | C# `enum : int` 或直接用 int 常量 | |
|
|||
|
|
|
|||
|
|
`[DllImport]` 模板:
|
|||
|
|
```csharp
|
|||
|
|
[DllImport("PCIe8586M.dll", // ← DLL 名以实际为准(看驱动安装目录/Samples 链接的 .lib)
|
|||
|
|
CallingConvention = CallingConvention.StdCall, // ← VC 常是 __stdcall;若 .h 里是 __cdecl 则改 Cdecl
|
|||
|
|
CharSet = CharSet.Ansi,
|
|||
|
|
SetLastError = true)]
|
|||
|
|
private static extern IntPtr 函数名(参数);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.1 已知坑
|
|||
|
|
|
|||
|
|
1. **调用约定**:阿尔泰 DLL 多为 `__stdcall`(=`StdCall`)。若崩溃在调用后栈不平衡(`PInvokeStackImbalance` 调试警告),改 `CallingConvention`。看 `.h` 里是否有 `WINAPI`/`__stdcall`/`__cdecl` 宏。
|
|||
|
|
2. **结构体对齐**:若 `.h` 里有 `#pragma pack(1)`,C# struct 要 `[StructLayout(LayoutKind.Sequential, Pack = 1)]`。错了会导致字段错位、参数乱掉。
|
|||
|
|
3. **BOOL ≠ bool**:C 的 `BOOL` 是 4 字节。直接用 C# `bool`(1字节) 编组会错位,用 `UnmanagedType.Bool` 或声明成 `int`。
|
|||
|
|
4. **缓冲区生命周期**:读 DMA 的缓冲必须 pinned 或非托管堆分配,传指针期间不能被 GC 移动。循环里复用同一块缓冲,别每块 new。
|
|||
|
|
5. **句柄无效判断**:`CreateDevice` 失败可能返回 `0`、`-1` 或 `0xFFFFFFFF`,看 Samples 里怎么判断的,照抄。
|
|||
|
|
6. **x86/x64**:DLL 是 64 位 → 工程必须 x64,否则 `BadImageFormatException`。
|
|||
|
|
7. **DLL 找不到**:运行时把 SDK 的 DLL 放 exe 同目录(或加 PATH)。开发期 DLL 不在本机属正常,模拟卡不需要它。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. 性能与内存约束
|
|||
|
|
|
|||
|
|
- 8 通道 @ 100MS/s = 1600 MB/s 原始数据(手册 4.9)。**调试程序不要全速 8 通道连续显示**,会爆。测试时用降速(如 1MS/s)或少通道。
|
|||
|
|
- **显示降采样**:ScottPlot 一屏最多画几千个点。来一块几万点的数据,画之前做 min/max 抽稀(每像素列取最值),否则渲染卡。ScottPlot 的 `Signal` 类型对密集点有内建优化,优先用。
|
|||
|
|
- **录制/剪辑缓冲上限**:`RingHistoryBuffer` 按"最近 N 秒 × 采样率 × 通道数 × 4字节(float)"预估,设硬上限(如 ≤ 500MB),超出丢最旧。UI 标明可剪辑的历史长度。
|
|||
|
|
- **采集频率 vs 渲染频率解耦**:见 §5.4,渲染固定 ~20Hz。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. 存盘格式(与未来 Python 分析对齐)
|
|||
|
|
|
|||
|
|
每次录制/剪辑输出两个文件:
|
|||
|
|
|
|||
|
|
- `{name}.bin`:`float32` 小端,按通道**交错**:`CH0[0]CH1[0]..CHn[0]CH0[1]..`(电压,单位 V)。
|
|||
|
|
- `{name}.json`:元数据头:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"channel_count": 8,
|
|||
|
|
"sample_rate_hz": 1000000.0,
|
|||
|
|
"input_range": "5V",
|
|||
|
|
"samples_per_channel": 10000,
|
|||
|
|
"dtype": "float32",
|
|||
|
|
"layout": "interleaved",
|
|||
|
|
"unit": "volt",
|
|||
|
|
"recorded_at": "ISO8601",
|
|||
|
|
"duration_ms": 10.0
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> Python 读法(未来):`np.fromfile(bin, dtype='<f4').reshape(-1, channel_count).T` 即得 [通道][点]。本程序阶段不实现 Python。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. 推荐开发顺序(里程碑)
|
|||
|
|
|
|||
|
|
1. **M1 骨架编译通过**:建工程(x64/WPF) + `Models` + `IDigitizer` + 空 `SimulatedDigitizer`,能 `dotnet build`。
|
|||
|
|
2. **M2 模拟卡出数据**:`CodeConverter` + `SimulatedDigitizer` 生成交错码值并解析,控制台/单测打印验证电压正确(§4 验收点)。
|
|||
|
|
3. **M3 UI 实时显示**:MainWindow + ScottPlot 曲线 + 表格,跑模拟卡看到滚动波形,验证 §5.4 的"采集高频渲染定频"不卡。
|
|||
|
|
4. **M4 录制与剪辑**:`RingHistoryBuffer` + `Recorder` + `WaveformWriter`,验证 §3.2 录制点数精确、剪辑正确。
|
|||
|
|
5. **M5 设备识别 UI**:自动枚举 + 手动逻辑号(仍走模拟卡)。
|
|||
|
|
6. **M6 真卡接入**:读 `Samples/VC/Include` 与 `Continue` 示例,实现 `Pcie8586Digitizer`,照 §5.2/§6 翻译 P/Invoke。换掉模拟卡,连真硬件联调。
|
|||
|
|
7. **M7 抽离复用层**:确认 `Hardware`+`Acquisition` 无 WPF 依赖,整理成可被主项目引用的形态。
|
|||
|
|
|
|||
|
|
每个里程碑用模拟卡保证可独立验证,真硬件只在 M6 才需要。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. 验收清单
|
|||
|
|
|
|||
|
|
- [ ] x64 编译,模拟卡下无硬件即可运行。
|
|||
|
|
- [ ] 码值换算单测通过(0x8000→0V 等)。
|
|||
|
|
- [ ] 解交错对 1/2/4/8 通道均正确。
|
|||
|
|
- [ ] 实时曲线 + 表格在模拟卡 1MS/s 下流畅(UI 不卡)。
|
|||
|
|
- [ ] 采样率 UI 只允许 100e6/N 离散值,旁显实际值。
|
|||
|
|
- [ ] 定时录制 1ms/10ms/100ms/1s/10s,落盘点数精确。
|
|||
|
|
- [ ] 剪辑任意 [起,止) 区间另存正确。
|
|||
|
|
- [ ] 存盘 .bin/.json 格式符合 §8。
|
|||
|
|
- [ ] `Hardware`+`Acquisition` 两层不含任何 WPF/UI 引用。
|
|||
|
|
- [ ] 真卡实现仅 `Pcie8586Digitizer.cs` 含 `[DllImport]`。
|
|||
|
|
- [ ] 真卡 CreateDevice→Configure→Start→读DMA→Stop→Release 全流程联通。
|