SSPC-Tester/Driver/PCIE8586/Docs/PCIe8586_采集调试程序_开发文档.md

310 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 是 VCC/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.02025/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随采集刷新节流到 ~1020 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 MainViewModelUI 线程安全是重点)
-`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 全流程联通。