SSPC-Tester/Docs/PCIe8586驱动接入资料/PCIe8586_采集调试程序_开发文档.md

310 lines
18 KiB
Markdown
Raw Normal View History

# 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 全流程联通。