SSPC-Tester/Docs/PCIe8586驱动接入资料/PCIe8586_采集调试程序_开发文档.md
2026-06-30 12:10:29 +08:00

18 KiB
Raw Permalink Blame History

PCIe8586M 采集调试程序 — 开发文档

给本机 Claude Code 的工作指令。本程序是「项目接入前测试探针」,目标是摸清阿尔泰 PCIe8586M 高速数字化仪的驱动行为,为主项目(.NET 8 + WPF + MVVM提供经过验证的接入方案与精确配置。


0. 给 Claude Code 的首要约定

  1. 函数名/结构体/常量的唯一权威是 Samples/ 目录下的 VC 示例和 Samples/VC/Include/*.h 头文件。 本文档刻意不写死任何 SDK 函数名。凡涉及具体 APIgrep/阅读 Samples/VC/IncludeSamples/VC/Simple/AD/ContinueFinite,以头文件为准。
  2. Samples 是 VCC/C++)的。 C# 接入靠 P/Invoke.hextern "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 硬件 IDPCI\VEN_1E42&DEV_8586(厂商号 0x1E42 = 阿尔泰,设备号 0x8586 驱动版本1.6.2.02025/7/20

项目 来源
通道数 8单端 手册 2.3.2
ADC 分辨率 16 bit8586M 手册 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 / N100M、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 → 0Vcode=0xFFFF → +5V 量程下约 +4.99985Vcode=05V。


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.DeinterleaveSampleBlock → 回调。
  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 + pinningushort[] 大缓冲建议 IntPtr + Marshal.Copy
struct { ... }(参数结构体) [StructLayout(LayoutKind.Sequential)] struct 字段顺序、类型、对齐必须 1:1
enum C# enum : int 或直接用 int 常量

[DllImport] 模板:

[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 ≠ boolC 的 BOOL 是 4 字节。直接用 C# bool(1字节) 编组会错位,用 UnmanagedType.Bool 或声明成 int
  4. 缓冲区生命周期:读 DMA 的缓冲必须 pinned 或非托管堆分配,传指针期间不能被 GC 移动。循环里复用同一块缓冲,别每块 new。
  5. 句柄无效判断CreateDevice 失败可能返回 0-10xFFFFFFFF,看 Samples 里怎么判断的,照抄。
  6. x86/x64DLL 是 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}.binfloat32 小端,按通道交错CH0[0]CH1[0]..CHn[0]CH0[1]..(电压,单位 V
  • {name}.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/IncludeContinue 示例,实现 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 全流程联通。