版本:v3.0.1

适用对象:使用 PynectDirect 动态库进行二次开发的用户
适用场景:入门级学生、实验室研究人员、企业软件开发人员
运行环境:64 位 Windows7/10/11、64 位 Python 3(如使用 Python 集成)
配套文件:bin/PynectDirect.dllbin/ftd2xx.dllinclude/pynect_direct.h
下载链接:PynectDirect SDK


目录

  1. SDK 简介
  2. 快速开始
  3. 基础概念
  4. 通用数据类型与常量
  5. C 语言例程
  6. C++ 语言例程
  7. C# 语言例程
  8. Python 语言例程
  9. 完整 API 参考
  10. 典型应用流程
  11. 常见问题(FAQ)
  12. 错误码与故障排查

1. SDK 简介

PynectDirect 是谱研互联(深圳谱研互联科技有限公司)光纤光谱仪的 Windows 动态库 SDK,仅供本公司光纤光谱仪使用,不支持其他厂家的光纤光谱仪。您只需要两件事就可以开始开发:

  • 动态库PynectDirect.dll(SDK 本体)+ ftd2xx.dll(FTDI USB 驱动,必须与 SDK 同目录)
  • 头文件pynect_direct.h(C 语言函数声明,是所有语言的签名权威来源)

SDK 通过 USB 直接与光纤光谱仪通信,提供 58 个公开函数,覆盖:设备发现与打开(支持同时打开多台)、参数设置(积分时间、平均次数、氙灯、触发)、波长与光谱数据采集、设备状态读取、Flash 与固化配置等功能。

1.1 支持设备

设备类型 说明
SPEC-CDS350、SPEC-CMS960 紫外可见光纤光谱仪
NIR-F950 近红外光纤光谱仪

打开设备后 SDK 自动识别设备类型。基础采谱流程两类设备相同;氙灯、外触发、DAC、固化配置等高级功能仅特定设备支持,调用前应查询设备能力(见 4.3 节)。

1.2 语言支持

SDK 导出标准 C ABI(extern "C"__cdecl),因此任何支持 C ABI 的语言都可以直接调用:

语言 集成方式 例程位置
C 链接 lib/libPynectDirect.dll.a(或静态库) 第 5 章
C++ 直接包含 pynect_direct.h(自动 extern "C" 第 6 章
C# DllImport("PynectDirect.dll") P/Invoke 第 7 章
Python ctypes 加载 DLL 第 8 章
其他 任何支持 C ABI 的语言(Rust/Go/Delphi 等) 按头文件签名声明即可

1.3 数据流模型(重要)

  • 设备每次传输全量像素:一次读取返回所有像素点的波长或强度(例如 2048 点)。
  • 波长范围来自全量数据pynect_direct_get_full_wavelength_range 返回完整波长数组的第一个和最后一个点(即全部像素的最小/最大波长),该值必然正确。
  • 指定波段在上位机截取pynect_direct_set_selected_wavelength_range 只在上位机记录一个像素窗口,get_selected_* 系列接口从全量数据中截取窗口内容返回,不会修改设备内置输出范围

因此,切换显示波段只需在上位机完成,无需与设备交互,快速且不影响其他程序对设备的访问。


2. 快速开始

2.1 文件布局

推荐把 SDK 文件整理为如下结构(以 Python 为例,其他语言同理):

MyProject/
|-- 您的代码文件(xxx.py / xxx.c / xxx.cpp / xxx.cs / ...)
`-- dll/
    |-- PynectDirect.dll
    `-- ftd2xx.dll

PynectDirect.dllftd2xx.dll 必须放在同一个文件夹中。

  • C/C++:将 include 目录加入头文件搜索路径,运行时 DLL 位于程序目录或系统 PATH。
  • C#:DLL 放在程序输出目录(exe 旁),或使用绝对路径。
  • Python:在脚本中用 os.add_dll_directory()dll 文件夹加入搜索路径后再 ctypes.CDLL 加载。

2.2 环境要求

项目 要求
操作系统 64 位 Windows
语言运行时 均为 64 位(32 位进程无法加载 64 位 DLL)
C/C++ 编译 MinGW-w64 GCC 或 MSVC(64 位)
C# .NET Framework 4.x 或 .NET 6+(x64 目标平台)
Python 64 位 Python 3

2.3 验证运行环境

Python 快速验证(无需连接设备):

python -c "import ctypes; d=ctypes.CDLL(r'dll\PynectDirect.dll'); print('load ok, api =', d.pynect_direct_scan_devices())"

能打印 load ok, api = 0 或设备数量即说明 DLL 加载成功。

完整硬件验证(需连接设备):

python .\examples\python\minimal_test.py --read-only

应看到 FAIL=0。只读模式不会修改设备任何参数。

2.4 连接设备

  1. 将光纤光谱仪通过 USB 连接到电脑。
  2. 等待 Windows 完成驱动安装(FTDI 驱动需已安装)。
  3. 关闭其他正在使用该光纤光谱仪的软件(设备为独占访问)。
  4. 运行自己的程序。

3. 基础概念

3.1 调用顺序

所有程序的调用顺序基本相同:

1. pynect_direct_scan_devices()        扫描设备,确认存在
2. (可选) get_chip_serial_by_index()   查看各设备芯片序列号
3. pynect_direct_open_device(0)        打开设备(或按序列号打开)
4. get_device_info() / get_capabilities()  读取设备信息与能力
5. get_wavelength_count()              获取数据点数 N
6. get_full_wavelength_data()          获取波长坐标(double 数组,N 个)
7. get_full_spectrum_data()            采集强度(uint16 数组,N 个)
8. (可选) set_selected_wavelength_range() 设置显示波段,然后 get_selected_*
9. pynect_direct_close_device()        关闭设备

3.2 返回值约定

  • 绝大多数函数成功返回 0,失败返回负错误码(见第 12 章)。
  • 例外:
  • pynect_direct_scan_devices:返回设备数量,0 表示没有设备(不是错误)。
  • pynect_direct_flash_read / pynect_direct_external_spectrum_control:成功时返回实际读取字节数。
  • pynect_direct_is_open:返回 1(打开)/ 0(关闭)。
  • pynect_direct_close_device:无返回值(void)。

3.3 数组容量约定

  • 波长数组元素类型:double(8 字节)
  • 强度数组元素类型:uint16(2 字节)
  • 数组长度:调用 pynect_direct_get_wavelength_count 得到 N,然后用 N 分配数组,把 N 作为 max_len 传入。
  • 字符串缓冲区:建议分配 64 字节max_len 包含结尾的 \0 空间。

3.4 会话规则

  • 支持同时打开多台设备(最多 PYNECT_DIRECT_MAX_DEVICES = 4 台)。pynect_direct_open_device 打开新设备时不会关闭已打开的设备。
  • SDK 采用"当前设备"模型:所有参数和数据 API 操作的是当前选中的设备
  • pynect_direct_open_device(0) → 打开设备 0,并设为当前设备
  • pynect_direct_open_device(1) → 打开设备 1,并设为当前设备(设备 0 保持打开)
  • pynect_direct_select_device(0) → 切换当前设备为设备 0
  • pynect_direct_get_open_device_count() → 当前已打开设备数
  • pynect_direct_close_device() → 关闭当前选中的设备
  • pynect_direct_close_all_devices() → 关闭全部设备
  • 重复打开同一台设备(同芯片序列号)不会重复建句柄,只切换为当前设备。
  • 每台设备有独立的状态:协议、能力、参数(积分时间、平均次数)、选定波段窗口互不影响。
  • 当前会话参数(积分时间、平均次数等)在关闭/复位/重新上电后不保留。
  • 固化配置(stored_* 系列)是设备的非易失参数,写入后掉电不丢失,会影响以后上电默认值,请谨慎使用。
  • 设备复位(reset_device)后,该设备已设置的选定波段窗口失效,需要重新设置。

4. 通用数据类型与常量

4.1 错误码

含义
PYNECT_DIRECT_SUCCESS 0 成功
PYNECT_DIRECT_ERR_NOT_INITIALIZED -1 未初始化(如未设置选定波段就读取)
PYNECT_DIRECT_ERR_DEVICE_NOT_FOUND -2 未发现设备或索引越界
PYNECT_DIRECT_ERR_DEVICE_NOT_OPEN -3 设备未打开
PYNECT_DIRECT_ERR_COMM_FAIL -4 通信失败
PYNECT_DIRECT_ERR_INVALID_PARAM -5 参数无效
PYNECT_DIRECT_ERR_OPEN_FAILED -6 打开设备失败(被占用/驱动异常)
PYNECT_DIRECT_ERR_CRC -7 响应 CRC 校验失败
PYNECT_DIRECT_ERR_PREAMBLE -8 响应帧头不匹配
PYNECT_DIRECT_ERR_TIMEOUT -9 设备响应超时
PYNECT_DIRECT_ERR_WPROT -10 写保护错误
PYNECT_DIRECT_ERR_UNSUPPORTED -11 当前设备不支持该功能

4.2 协议枚举

typedef enum PynectDirectDeviceProtocol {
    PYNECT_DIRECT_PROTOCOL_AUTO = 0,        // 自动识别(默认推荐)
    PYNECT_DIRECT_PROTOCOL_UVVIS_CM2 = 1,   // UV/VIS
    PYNECT_DIRECT_PROTOCOL_NIR_LEGACY = 2,  // NIR
    PYNECT_DIRECT_PROTOCOL_UNKNOWN = 255
} PynectDirectDeviceProtocol;

4.3 能力位(capabilities)

pynect_direct_get_capabilities 返回 32 位掩码,判断设备支持哪些功能:

功能
0x00000001 PYNECT_DIRECT_CAP_INTEGRATION_TIME 积分时间
0x00000002 PYNECT_DIRECT_CAP_AVERAGE_NUMBER 平均次数
0x00000004 PYNECT_DIRECT_CAP_LEVEL_OUTPUT 电平输出
0x00000008 PYNECT_DIRECT_CAP_FLASH Flash 读写
0x00000010 PYNECT_DIRECT_CAP_WAVELENGTH_DATA 波长数据
0x00000020 PYNECT_DIRECT_CAP_SPECTRUM_DATA 光谱数据
0x00000040 PYNECT_DIRECT_CAP_TEMPERATURE 温度
0x00000080 PYNECT_DIRECT_CAP_HARDWARE_VERSION 硬件版本
0x00000100 PYNECT_DIRECT_CAP_CALIBRATION 标定系数
0x00000200 PYNECT_DIRECT_CAP_XENON 氙灯
0x00000400 PYNECT_DIRECT_CAP_USB_STATUS USB 状态
0x00000800 PYNECT_DIRECT_CAP_EXTERNAL_TRIGGER 外触发
0x00001000 PYNECT_DIRECT_CAP_DAC DAC
0x00002000 PYNECT_DIRECT_CAP_STORED_CONFIG 固化配置
0x00004000 PYNECT_DIRECT_CAP_SLIT_WIDTH 狭缝宽度

4.4 常用常量

// 氙灯模式
#define XENON_OFF         0x00   // 关闭
#define XENON_CONTINUOUS  0x01   // 连续
#define XENON_SINGLE      0x81   // 单次

// 外触发类型
#define TRIGGER_OFF       0x00   // 关闭
#define TRIGGER_ON_RISE   0xAA   // 上升沿触发
#define TRIGGER_ON_LEVEL  0xBB   // 电平触发

4.5 数据结构

设备信息:

typedef struct PynectDirectDeviceInfo {
    PynectDirectDeviceProtocol protocol;   // 实际协议类型
    uint32_t capabilities;                 // 能力位
    char protocol_name[32];                // 协议名称字符串
    char model[32];                        // 型号字符串
    char serial_number[32];                // 业务序列号字符串
    char hardware_version[64];             // 硬件版本字符串
} PynectDirectDeviceInfo;

选定波段信息(get_selected_wavelength_range 返回):

typedef struct PynectDirectSelectedWavelengthRange {
    double   request_start_nm;   // 请求的起始波长(nm)
    double   request_end_nm;     // 请求的终止波长(nm)
    double   actual_start_nm;    // 实际截取的起始波长(对应像素的真实波长)
    double   actual_end_nm;      // 实际截取的终止波长
    uint16_t start_pixel;        // 起始像素索引(从 0 开始)
    uint16_t end_pixel;          // 终止像素索引
    uint16_t count;              // 窗口内像素点数
} PynectDirectSelectedWavelengthRange;

5. C 语言例程

5.1 编译与运行

# 进入例程目录(依赖的 DLL、头文件、库均已放入该目录)
cd examples/c

# 方式一:直接运行(已预编译,无需安装任何开发环境)
spectrum_demo.exe

# 方式二:自行编译(MinGW-w64,64 位)
gcc spectrum_demo.c -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe

程序运行结束后会暂停并提示"请按任意键继续",按任意键即可退出窗口,方便查看结果(源码中为 system("pause"))。

使用 MSVC 时:cl spectrum_demo.c /Iinclude /link /LIBPATH:lib(需 .lib 导入库,可联系厂商获取;GCC 环境使用 libPynectDirect.dll.a)。

完整源码见交付包 examples/c/spectrum_demo.c(即 5.2 节内容)。

5.2 完整源码(spectrum_demo.c)

#define PYNECT_DIRECT_USE_DLL      /* 使用动态库时定义 */
#include "pynect_direct.h"
#include <stdio.h>
#include <stdlib.h>

static int require(int code, const char* what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        printf("[FAIL] %s: return code = %d\n", what, code);
        return 0;
    }
    return 1;
}

int main(void)
{
    PynectDirectDeviceInfo info;
    PynectDirectSelectedWavelengthRange sel;
    uint16_t count = 0, i;
    double* wavelengths = NULL;
    uint16_t* spectrum = NULL;
    double start_nm = 0.0, end_nm = 0.0;
    unsigned int integration = 0;
    int n;

    /* 1. 扫描设备 */
    n = pynect_direct_scan_devices();
    if (n <= 0) {
        printf("No device found.\n");
        return 1;
    }
    printf("Found %d device(s).\n", n);

    /* 2. 打开设备(索引 0) */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. 读取设备信息 */
    if (require(pynect_direct_get_device_info(&info), "get_device_info")) {
        printf("Protocol: %s\n", info.protocol_name);
        printf("Model:    %s\n", info.model);
        printf("Serial:   %s\n", info.serial_number);
        printf("Hardware: %s\n", info.hardware_version);
    }

    /* 4. 数据点数 */
    if (!require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count")) goto done;
    printf("Wavelength count: %u\n", count);
    if (count == 0) goto done;

    /* 5. 波长范围(全量波长数组首尾,必然正确) */
    if (!require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm), "get_full_wavelength_range")) goto done;
    printf("Wavelength range: %.2f ~ %.2f nm\n", start_nm, end_nm);

    /* 6. 分配数组 */
    wavelengths = (double*)malloc(count * sizeof(double));
    spectrum = (uint16_t*)malloc(count * sizeof(uint16_t));
    if (!wavelengths || !spectrum) {
        printf("Out of memory.\n");
        goto done;
    }

    /* 7. 读取波长坐标 */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. 设置并读取积分时间(临时,不写入固化配置) */
    if (require(pynect_direct_get_integration_time(&integration), "get_integration_time"))
        printf("Current integration time: %u us\n", integration);
    pynect_direct_set_integration_time(50000);   /* 50 ms */

    /* 9. 采集光谱 */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. 两列打印:波长 + 对应强度(前 5 点 + 后 5 点) */
    printf("%6s  %13s  %9s\n", "Index", "Wavelength(nm)", "Intensity");
    printf("%6s  %13s  %9s\n", "-----", "--------------", "---------");
    for (i = 0; i < 5 && i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);
    if (count > 10) printf("...  (total %u points)\n", count);
    for (i = count > 5 ? count - 5 : 0; i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);

    /* 11. 指定波段(上位机截取,不修改设备) */
    if (start_nm < 400.0 && end_nm > 800.0) {
        if (require(pynect_direct_set_selected_wavelength_range(400.0, 800.0), "set_selected_range")) {
            if (require(pynect_direct_get_selected_wavelength_range(&sel), "get_selected_range")) {
                printf("Selected: pixels %u..%u, count=%u, actual %.2f..%.2f nm\n",
                       sel.start_pixel, sel.end_pixel, sel.count,
                       sel.actual_start_nm, sel.actual_end_nm);
            }
        }
    } else {
        printf("Device does not cover 400-800 nm, skip selected range.\n");
    }

    /* 12. 关闭设备 */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    return 0;
}
#define PYNECT_DIRECT_USE_DLL      /* 使用动态库时定义 */
#include "pynect_direct.h"
#include <stdio.h>
#include <stdlib.h>

static int require(int code, const char* what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        printf("[FAIL] %s: return code = %d\n", what, code);
        return 0;
    }
    return 1;
}

int main(void)
{
    PynectDirectDeviceInfo info;
    PynectDirectSelectedWavelengthRange sel;
    uint16_t count = 0, i;
    double* wavelengths = NULL;
    uint16_t* spectrum = NULL;
    double start_nm = 0.0, end_nm = 0.0;
    unsigned int integration = 0;
    int n;

    /* 1. 扫描设备 */
    n = pynect_direct_scan_devices();
    if (n <= 0) {
        printf("No device found.\n");
        system("pause");
        return 1;
    }
    printf("Found %d device(s).\n", n);

    /* 2. 打开设备(索引 0) */
    if (!require(pynect_direct_open_device(0), "open_device")) goto done;

    /* 3. 读取设备信息 */
    if (require(pynect_direct_get_device_info(&info), "get_device_info")) {
        printf("Protocol: %s\n", info.protocol_name);
        printf("Model:    %s\n", info.model);
        printf("Serial:   %s\n", info.serial_number);
        printf("Hardware: %s\n", info.hardware_version);
    }

    /* 4. 数据点数 */
    if (!require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count")) goto done;
    printf("Wavelength count: %u\n", count);
    if (count == 0) goto done;

    /* 5. 波长范围(全量波长数组首尾,必然正确) */
    if (!require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm), "get_full_wavelength_range")) goto done;
    printf("Wavelength range: %.2f ~ %.2f nm\n", start_nm, end_nm);

    /* 6. 分配数组 */
    wavelengths = (double*)malloc(count * sizeof(double));
    spectrum = (uint16_t*)malloc(count * sizeof(uint16_t));
    if (!wavelengths || !spectrum) {
        printf("Out of memory.\n");
        goto done;
    }

    /* 7. 读取波长坐标 */
    if (!require(pynect_direct_get_full_wavelength_data(wavelengths, count), "get_full_wavelength_data")) goto done;

    /* 8. 设置并读取积分时间(临时,不写入固化配置) */
    if (require(pynect_direct_get_integration_time(&integration), "get_integration_time"))
        printf("Current integration time: %u us\n", integration);
    pynect_direct_set_integration_time(50000);   /* 50 ms */

    /* 9. 采集光谱 */
    if (!require(pynect_direct_get_full_spectrum_data(spectrum, count), "get_full_spectrum_data")) goto done;

    /* 10. 两列打印:波长 + 对应强度(前 5 点 + 后 5 点) */
    printf("%6s  %13s  %9s\n", "Index", "Wavelength(nm)", "Intensity");
    printf("%6s  %13s  %9s\n", "-----", "--------------", "---------");
    for (i = 0; i < 5 && i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);
    if (count > 10) printf("...  (total %u points)\n", count);
    for (i = count > 5 ? count - 5 : 0; i < count; i++)
        printf("%6u  %13.3f  %9u\n", i, wavelengths[i], spectrum[i]);

    /* 11. 指定波段(上位机截取,不修改设备) */
    if (start_nm < 400.0 && end_nm > 800.0) {
        if (require(pynect_direct_set_selected_wavelength_range(400.0, 800.0), "set_selected_range")) {
            if (require(pynect_direct_get_selected_wavelength_range(&sel), "get_selected_range")) {
                printf("Selected: pixels %u..%u, count=%u, actual %.2f..%.2f nm\n",
                       sel.start_pixel, sel.end_pixel, sel.count,
                       sel.actual_start_nm, sel.actual_end_nm);
            }
        }
    } else {
        printf("Device does not cover 400-800 nm, skip selected range.\n");
    }

    /* 12. 关闭设备 */
done:
    free(wavelengths);
    free(spectrum);
    pynect_direct_close_device();
    printf("Done.\n");
    system("pause");
    return 0;
}

5.3 输出示例

Found 1 device(s).
Protocol: UVVIS_CM2
Model:    PYNECT-UV-2048
Serial:   SP123456
Hardware: 1.0
Wavelength count: 2048
Wavelength range: 199.04 ~ 1019.50 nm
Current integration time: 10000 us
Index  Wavelength(nm)  Intensity
-----  --------------  ---------
     0          199.04        128
     1          200.08        130
     2          201.12        127
     3          202.16        125
     4          203.20        131
...  (total 2048 points)
  2043         1018.46        122
  2044         1019.50        121
  2045         1020.54        119
  2046         1021.58        124
  2047         1022.62        126
Selected: pixels 808..1616, count=809, actual 400.00..800.00 nm
Done.
请按任意键继续. . .

6. C++ 语言例程

6.1 编译与运行

cd examples/cpp

# 方式一:直接运行(已预编译)
spectrum_demo.exe

# 方式二:自行编译
g++ spectrum_demo.cpp -Iinclude -Llib -lPynectDirect -o spectrum_demo.exe

程序运行结束后会暂停并提示"请按任意键继续",按任意键退出窗口(源码中为 system("pause"))。

头文件已包含 extern "C",C++ 直接 #include "pynect_direct.h" 即可。

完整源码见交付包 examples/cpp/spectrum_demo.cpp(即 6.2 节内容)。

6.2 完整源码(spectrum_demo.cpp)

#define PYNECT_DIRECT_USE_DLL
#include "pynect_direct.h"
#include <iomanip>
#include <iostream>
#include <vector>
#include <string>

static void require(int code, const std::string& what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        std::cerr << "[FAIL] " << what << ": return code = " << code << std::endl;
        throw std::runtime_error(what);
    }
}

int main()
{
    try {
        /* 1. 扫描设备 */
        int n = pynect_direct_scan_devices();
        if (n <= 0) {
            std::cout << "No device found." << std::endl;
            return 1;
        }
        std::cout << "Found " << n << " device(s)." << std::endl;

        /* 2. 打开设备 */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. 设备信息 */
        PynectDirectDeviceInfo info{};
        require(pynect_direct_get_device_info(&info), "get_device_info");
        std::cout << "Model:   " << info.model << std::endl
                  << "Serial:  " << info.serial_number << std::endl
                  << "Hardware:" << info.hardware_version << std::endl;

        /* 4. 数据点数 */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. 波长范围 */
        double start_nm = 0.0, end_nm = 0.0;
        require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm),
                "get_full_wavelength_range");
        std::cout << "Wavelength range: " << start_nm << " ~ " << end_nm << " nm" << std::endl;

        /* 6. 读取波长坐标 */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. 设置积分时间并采集光谱 */
        pynect_direct_set_integration_time(50000);   /* 50 ms,临时设置 */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. 两列打印:波长 + 对应强度(前 5 点 + 后 5 点) */
        std::cout << std::setw(6) << "Index" << std::setw(15) << "Wavelength(nm)"
                  << std::setw(11) << "Intensity" << std::endl;
        std::cout << std::setw(6) << "-----" << std::setw(15) << "--------------"
                  << std::setw(11) << "---------" << std::endl;
        for (int i = 0; i < 5; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;
        if (count > 10)
            std::cout << "...  (total " << count << " points)" << std::endl;
        for (int i = count - 5; i < count; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;

        /* 9. 指定波段(上位机截取) */
        if (start_nm < 400.0 && end_nm > 800.0) {
            require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range");
            PynectDirectSelectedWavelengthRange sel{};
            require(pynect_direct_get_selected_wavelength_range(&sel),
                    "get_selected_range");
            std::cout << "Selected: pixels " << sel.start_pixel << ".."
                      << sel.end_pixel << ", count=" << sel.count << std::endl;
        }

        /* 10. 关闭设备 */
        pynect_direct_close_device();
        std::cout << "Done." << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}
#define PYNECT_DIRECT_USE_DLL
#include "pynect_direct.h"
#include <iomanip>
#include <iostream>
#include <vector>
#include <string>

static void require(int code, const std::string& what)
{
    if (code != PYNECT_DIRECT_SUCCESS) {
        std::cerr << "[FAIL] " << what << ": return code = " << code << std::endl;
        throw std::runtime_error(what);
    }
}

int main()
{
    try {
        /* 1. 扫描设备 */
        int n = pynect_direct_scan_devices();
        if (n <= 0) {
            std::cout << "No device found." << std::endl;
            system("pause");
            return 1;
        }
        std::cout << "Found " << n << " device(s)." << std::endl;

        /* 2. 打开设备 */
        require(pynect_direct_open_device(0), "open_device");

        /* 3. 设备信息 */
        PynectDirectDeviceInfo info{};
        require(pynect_direct_get_device_info(&info), "get_device_info");
        std::cout << "Model:   " << info.model << std::endl
                  << "Serial:  " << info.serial_number << std::endl
                  << "Hardware:" << info.hardware_version << std::endl;

        /* 4. 数据点数 */
        uint16_t count = 0;
        require(pynect_direct_get_wavelength_count(&count), "get_wavelength_count");
        std::cout << "Wavelength count: " << count << std::endl;

        /* 5. 波长范围 */
        double start_nm = 0.0, end_nm = 0.0;
        require(pynect_direct_get_full_wavelength_range(&start_nm, &end_nm),
                "get_full_wavelength_range");
        std::cout << "Wavelength range: " << start_nm << " ~ " << end_nm << " nm" << std::endl;

        /* 6. 读取波长坐标 */
        std::vector<double> wavelengths(count);
        require(pynect_direct_get_full_wavelength_data(wavelengths.data(), count),
                "get_full_wavelength_data");

        /* 7. 设置积分时间并采集光谱 */
        pynect_direct_set_integration_time(50000);   /* 50 ms,临时设置 */
        std::vector<uint16_t> spectrum(count);
        require(pynect_direct_get_full_spectrum_data(spectrum.data(), count),
                "get_full_spectrum_data");

        /* 8. 两列打印:波长 + 对应强度(前 5 点 + 后 5 点) */
        std::cout << std::setw(6) << "Index" << std::setw(15) << "Wavelength(nm)"
                  << std::setw(11) << "Intensity" << std::endl;
        std::cout << std::setw(6) << "-----" << std::setw(15) << "--------------"
                  << std::setw(11) << "---------" << std::endl;
        for (int i = 0; i < 5; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;
        if (count > 10)
            std::cout << "...  (total " << count << " points)" << std::endl;
        for (int i = count - 5; i < count; ++i)
            std::cout << std::setw(6) << i << std::setw(15) << std::fixed
                      << std::setprecision(3) << wavelengths[i]
                      << std::setw(11) << spectrum[i] << std::endl;

        /* 9. 指定波段(上位机截取) */
        if (start_nm < 400.0 && end_nm > 800.0) {
            require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range");
            PynectDirectSelectedWavelengthRange sel{};
            require(pynect_direct_get_selected_wavelength_range(&sel),
                    "get_selected_range");
            std::cout << "Selected: pixels " << sel.start_pixel << ".."
                      << sel.end_pixel << ", count=" << sel.count << std::endl;
        }

        /* 10. 关闭设备 */
        pynect_direct_close_device();
        std::cout << "Done." << std::endl;
        system("pause");
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        system("pause");
        return 1;
    }
    return 0;
}

7. C# 语言例程

7.1 编译与运行

cd examples/csharp

# 方式一:直接运行(已预编译,.NET Framework 4.x,Windows 自带运行时)
spectrum_demo.exe

# 方式二:自行编译(.NET Framework 自带编译器,64 位)
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /platform:x64 spectrum_demo.cs

# 方式三:.NET SDK
dotnet build -c Release

程序运行结束后会提示"按任意键退出...",按任意键关闭窗口(源码中为 WaitExit())。

完整源码见交付包 examples/csharp/spectrum_demo.cs(即 7.2 节内容)。示例使用 C# 5 兼容写法,.NET Framework 4.x 与 .NET 6+ 均可编译。

7.2 完整源码(spectrum_demo.cs)

using System;
using System.Runtime.InteropServices;
using System.Text;

class PynectDirectDemo
{
    // ---------- 数据结构(与 pynect_direct.h 对应) ----------
    [StructLayout(LayoutKind.Sequential)]
    struct PynectDirectDeviceInfo
    {
        public int protocol;
        public uint capabilities;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string protocol_name;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string model;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string serial_number;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string hardware_version;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct PynectDirectSelectedWavelengthRange
    {
        public double request_start_nm;
        public double request_end_nm;
        public double actual_start_nm;
        public double actual_end_nm;
        public ushort start_pixel;
        public ushort end_pixel;
        public ushort count;
    }

    // ---------- DllImport(全部为 cdecl 调用约定) ----------
    const string DLL = "PynectDirect.dll";
    const int SUCCESS = 0;

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_scan_devices();

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_open_device(int index);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern void pynect_direct_close_device();

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_device_info(ref PynectDirectDeviceInfo info);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_wavelength_count(ref ushort count);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_wavelength_range(ref double start, ref double end);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_wavelength_data(double[] buffer, int max_len);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_full_spectrum_data(ushort[] buffer, int max_len);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_set_integration_time(uint us);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_integration_time(ref uint us);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_set_selected_wavelength_range(double start, double end);

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    static extern int pynect_direct_get_selected_wavelength_range(
        ref PynectDirectSelectedWavelengthRange range);

    static void Require(int code, string what)
    {
        if (code != SUCCESS)
            throw new InvalidOperationException(string.Format(
                "{0} failed, code={1}", what, code));
    }

    static void WaitExit()
    {
        Console.Write("按任意键退出...");
        try { Console.ReadKey(); }
        catch { /* 非交互环境(输入被重定向)时直接退出 */ }
    }

    static void Main()
    {
        try
        {
            int n = pynect_direct_scan_devices();
            if (n <= 0) { Console.WriteLine("No device found."); WaitExit(); return; }
            Console.WriteLine(string.Format("Found {0} device(s).", n));

            Require(pynect_direct_open_device(0), "open_device");

            var info = new PynectDirectDeviceInfo();
            Require(pynect_direct_get_device_info(ref info), "get_device_info");
            Console.WriteLine(string.Format(
                "Model: {0}, Serial: {1}, HW: {2}",
                info.model, info.serial_number, info.hardware_version));

            ushort count = 0;
            Require(pynect_direct_get_wavelength_count(ref count), "get_wavelength_count");
            Console.WriteLine(string.Format("Wavelength count: {0}", count));

            double start = 0, end = 0;
            Require(pynect_direct_get_full_wavelength_range(ref start, ref end),
                    "get_full_wavelength_range");
            Console.WriteLine(string.Format(
                "Wavelength range: {0:F2} ~ {1:F2} nm", start, end));

            var wavelengths = new double[count];
            Require(pynect_direct_get_full_wavelength_data(wavelengths, count),
                    "get_full_wavelength_data");

            uint integration = 0;
            Require(pynect_direct_get_integration_time(ref integration), "get_integration_time");
            Console.WriteLine(string.Format("Current integration time: {0} us", integration));
            pynect_direct_set_integration_time(50000);

            var spectrum = new ushort[count];
            Require(pynect_direct_get_full_spectrum_data(spectrum, count),
                    "get_full_spectrum_data");

            Console.WriteLine(string.Format(
                "{0,-6} {1,13} {2,9}", "Index", "Wavelength(nm)", "Intensity"));
            Console.WriteLine(string.Format(
                "{0,-6} {1,13} {2,9}", "-----", "--------------", "---------"));
            for (int i = 0; i < 5 && i < count; i++)
                Console.WriteLine(string.Format(
                    "{0,-6} {1,13:F3} {2,9}", i, wavelengths[i], spectrum[i]));
            if (count > 10)
                Console.WriteLine(string.Format("...  (total {0} points)", count));
            for (int i = (count > 5 ? count - 5 : 0); i < count; i++)
                Console.WriteLine(string.Format(
                    "{0,-6} {1,13:F3} {2,9}", i, wavelengths[i], spectrum[i]));

            if (start < 400.0 && end > 800.0)
            {
                Require(pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                        "set_selected_range");
                var sel = new PynectDirectSelectedWavelengthRange();
                Require(pynect_direct_get_selected_wavelength_range(ref sel),
                        "get_selected_range");
                Console.WriteLine(string.Format(
                    "Selected: pixels {0}..{1}, count={2}",
                    sel.start_pixel, sel.end_pixel, sel.count));
            }

            pynect_direct_close_device();
            Console.WriteLine("Done.");
            WaitExit();
        }
        catch (Exception e)
        {
            Console.WriteLine(string.Format("Error: {0}", e.Message));
            WaitExit();
        }
    }
}

8. Python 语言例程

8.1 环境

  • 64 位 Python 3(验证:python -c "import struct; print(8 * struct.calcsize('P'))",输出 64
  • 无需安装任何第三方库(只用标准库 ctypes

完整源码见交付包 examples/python/spectrum_demo.py(即 8.2 节内容)。在 examples/python/ 目录下运行,minimal_test.py 与两个 DLL 均在该目录。

8.2 完整源码(spectrum_demo.py)

import ctypes
import os
from pathlib import Path

DLL_DIR = Path(__file__).resolve().parent / "dll"   # 修改为您的 dll 目录

def load_sdk():
    """加载 DLL,并把 dll 目录加入依赖搜索路径(ftd2xx.dll 同目录)"""
    if os.name == "nt" and hasattr(os, "add_dll_directory"):
        os.add_dll_directory(str(DLL_DIR))
    return ctypes.CDLL(str(DLL_DIR / "PynectDirect.dll"))

# ---------- 数据结构 ----------
class PynectDirectDeviceInfo(ctypes.Structure):
    _fields_ = [
        ("protocol", ctypes.c_int),
        ("capabilities", ctypes.c_uint32),
        ("protocol_name", ctypes.c_char * 32),
        ("model", ctypes.c_char * 32),
        ("serial_number", ctypes.c_char * 32),
        ("hardware_version", ctypes.c_char * 64),
    ]

class PynectDirectSelectedWavelengthRange(ctypes.Structure):
    _fields_ = [
        ("request_start_nm", ctypes.c_double),
        ("request_end_nm", ctypes.c_double),
        ("actual_start_nm", ctypes.c_double),
        ("actual_end_nm", ctypes.c_double),
        ("start_pixel", ctypes.c_uint16),
        ("end_pixel", ctypes.c_uint16),
        ("count", ctypes.c_uint16),
    ]

P_U16 = ctypes.POINTER(ctypes.c_uint16)
P_UINT = ctypes.POINTER(ctypes.c_uint)
P_DOUBLE = ctypes.POINTER(ctypes.c_double)

# ---------- 类型绑定(本次例程用到的函数) ----------
def bind_api(sdk):
    sdk.pynect_direct_scan_devices.restype = ctypes.c_int
    sdk.pynect_direct_open_device.argtypes = [ctypes.c_int]
    sdk.pynect_direct_open_device.restype = ctypes.c_int
    sdk.pynect_direct_close_device.restype = None
    sdk.pynect_direct_get_device_info.argtypes = [ctypes.POINTER(PynectDirectDeviceInfo)]
    sdk.pynect_direct_get_device_info.restype = ctypes.c_int
    sdk.pynect_direct_get_wavelength_count.argtypes = [P_U16]
    sdk.pynect_direct_get_wavelength_count.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_range.argtypes = [P_DOUBLE, P_DOUBLE]
    sdk.pynect_direct_get_full_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_full_wavelength_data.argtypes = [P_DOUBLE, ctypes.c_int]
    sdk.pynect_direct_get_full_wavelength_data.restype = ctypes.c_int
    sdk.pynect_direct_get_full_spectrum_data.argtypes = [P_U16, ctypes.c_int]
    sdk.pynect_direct_get_full_spectrum_data.restype = ctypes.c_int
    sdk.pynect_direct_get_integration_time.argtypes = [P_UINT]
    sdk.pynect_direct_get_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_integration_time.argtypes = [ctypes.c_uint]
    sdk.pynect_direct_set_integration_time.restype = ctypes.c_int
    sdk.pynect_direct_set_selected_wavelength_range.argtypes = [ctypes.c_double, ctypes.c_double]
    sdk.pynect_direct_set_selected_wavelength_range.restype = ctypes.c_int
    sdk.pynect_direct_get_selected_wavelength_range.argtypes = [ctypes.POINTER(PynectDirectSelectedWavelengthRange)]
    sdk.pynect_direct_get_selected_wavelength_range.restype = ctypes.c_int

def require(code, what):
    if code != 0:
        raise RuntimeError(f"{what} failed, return code={code}")

def main():
    sdk = load_sdk()
    bind_api(sdk)

    n = sdk.pynect_direct_scan_devices()
    if n <= 0:
        print("No device found.")
        return
    print(f"Found {n} device(s).")

    require(sdk.pynect_direct_open_device(0), "open_device")
    try:
        info = PynectDirectDeviceInfo()
        require(sdk.pynect_direct_get_device_info(ctypes.byref(info)), "get_device_info")
        print(f"Model:    {info.model.decode().strip(chr(0))}")
        print(f"Serial:   {info.serial_number.decode().strip(chr(0))}")
        print(f"Hardware: {info.hardware_version.decode().strip(chr(0))}")

        count = ctypes.c_uint16()
        require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "get_wavelength_count")
        n = count.value
        print(f"Wavelength count: {n}")

        start, end = ctypes.c_double(), ctypes.c_double()
        require(sdk.pynect_direct_get_full_wavelength_range(
            ctypes.byref(start), ctypes.byref(end)), "get_full_wavelength_range")
        print(f"Wavelength range: {start.value:.2f} ~ {end.value:.2f} nm")

        wl = (ctypes.c_double * n)()
        require(sdk.pynect_direct_get_full_wavelength_data(wl, n), "get_full_wavelength_data")

        integration = ctypes.c_uint()
        require(sdk.pynect_direct_get_integration_time(ctypes.byref(integration)),
                "get_integration_time")
        print(f"Current integration time: {integration.value} us")
        sdk.pynect_direct_set_integration_time(50000)

        spec = (ctypes.c_uint16 * n)()
        require(sdk.pynect_direct_get_full_spectrum_data(spec, n), "get_full_spectrum_data")

        print(f"{'Index':<6}{'Wavelength(nm)':>15}{'Intensity':>10}")
        print(f"{'-----':<6}{'--------------':>15}{'---------':>10}")
        for i in range(min(5, n)):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")
        if n > 10:
            print(f"...  (total {n} points)")
        for i in range(max(0, n - 5), n):
            print(f"{i:<6}{wl[i]:>15.3f}{spec[i]:>10}")

        if start.value < 400.0 and end.value > 800.0:
            require(sdk.pynect_direct_set_selected_wavelength_range(400.0, 800.0),
                    "set_selected_range")
            sel = PynectDirectSelectedWavelengthRange()
            require(sdk.pynect_direct_get_selected_wavelength_range(ctypes.byref(sel)),
                    "get_selected_range")
            print(f"Selected: pixels {sel.start_pixel}..{sel.end_pixel}, count={sel.count}")
    finally:
        sdk.pynect_direct_close_device()
        print("Done.")

if __name__ == "__main__":
    main()

8.3 运行

python .\spectrum_demo.py

全部 58 个 API 的完整 Python 类型绑定见 examples/python/minimal_test.pyAPI_SIGNATURES,可直接复用。


9. 完整 API 参考

约定:成功返回 0(有说明的除外);* 标记输出参数;字符缓冲区建议 64 字节。

9.1 设备发现、打开与识别

函数 说明
pynect_direct_scan_devices() 刷新 USB 设备列表,打印每台设备索引、描述和芯片序列号,返回设备数量(不打开设备)
pynect_direct_get_chip_serial_by_index(index, chip_serial, max_len) 按扫描索引读取 USB 芯片序列号,用于稳定区分多台设备
pynect_direct_open_device(index) 按索引打开设备并识别协议与能力,设为当前设备;不关闭其他已打开设备(重复打开同设备只切换)
pynect_direct_open_device_by_chip_serial(chip_serial) 按芯片序列号打开匹配设备,适合索引可能变化的多设备系统
pynect_direct_select_device(device_index) 切换当前设备(0 起编号,与打开顺序一致),用于多设备场景
pynect_direct_get_open_device_count() 返回当前已打开设备数量
pynect_direct_close_device() 关闭当前选中的设备;重复调用允许
pynect_direct_close_all_devices() 关闭全部已打开设备
pynect_direct_is_open() 返回 1 当前设备打开 / 0 关闭(不与设备通信)
pynect_direct_set_protocol_preference(protocol) 设置打开设备时使用的协议偏好(默认 AUTO 自动识别)
pynect_direct_get_device_protocol(protocol*) 读取当前设备实际协议类型
pynect_direct_get_capabilities(capabilities*) 读取当前设备 32 位能力位掩码
pynect_direct_get_device_info(info*) 一次读取当前设备协议、能力、型号、序列号、硬件版本

9.2 积分时间与平均次数

函数 说明
pynect_direct_set_integration_time(us) 设置当前会话积分时间(微秒),不影响固化配置
pynect_direct_get_integration_time(us*) 读取当前会话积分时间
pynect_direct_set_average_number(count) 设置平均次数(1-255),次数多则噪声低、耗时增
pynect_direct_get_average_number(count*) 读取平均次数

9.3 波长与光谱数据(核心)

函数 说明
pynect_direct_get_wavelength_count(count*) 读取完整数据点数 N,决定数组容量
pynect_direct_get_full_wavelength_range(start_nm*, end_nm*) 读取完整波长数组的首尾(即所有像素最小/最大波长),按从小到大返回
pynect_direct_get_full_wavelength_data(buffer, max_len) 读取每个像素对应的完整波长数组(double)
pynect_direct_get_full_spectrum_data(buffer, max_len) 采集一次完整光谱,返回每个像素强度(uint16)
pynect_direct_set_selected_wavelength_range(start_nm, end_nm) 在上位机选择连续像素窗口(不修改设备输出),仅保存在当前会话
pynect_direct_get_selected_wavelength_range(out_range*) 读取选定波段的请求/实际边界、像素索引、点数
pynect_direct_get_selected_wavelength_data(buffer, max_len) 从全量波长中截取选定窗口
pynect_direct_get_selected_spectrum_data(buffer, max_len) 采集完整光谱后截取选定窗口的强度

9.4 设备状态与身份信息

函数 说明
pynect_direct_get_calibration_coefficients(coeffs[4]) 读取 4 个波长标定系数(只读)
pynect_direct_get_temperature(temp_c*) 读取设备内部温度(摄氏度)
pynect_direct_get_hardware_version(version_str, max_len) 读取硬件版本字符串
pynect_direct_get_serial_number(sn, max_len) 读取光纤光谱仪业务序列号
pynect_direct_get_detector_serial_number(sn, max_len) 读取探测器序列号(UV/VIS)
pynect_direct_get_device_model(model, max_len) 读取设备型号
pynect_direct_get_usb_status(connected*) 读取设备内部报告的 USB 连接状态
pynect_direct_set_usb_baud_rate(rate) 写入 USB 通信速率参数(16 位配置码)
pynect_direct_get_usb_baud_rate(rate*) 读取 USB 通信速率参数

9.5 氙灯、DAC 与电平输出

函数 说明
pynect_direct_set_xenon_pulse(period_10ns, high_10ns) 设置氙灯脉冲周期与高电平宽度(单位 10ns)
pynect_direct_get_xenon_pulse(period*, high*) 读取氙灯脉冲参数
pynect_direct_set_xenon_mode(mode) 设置氙灯模式(XENON_OFF/XENON_CONTINUOUS/XENON_SINGLE
pynect_direct_get_xenon_mode(mode*) 读取氙灯模式
pynect_direct_set_dac_voltage(dac_value) 设置 DAC 12 位原始输出码(非电压值)
pynect_direct_get_dac_voltage(dac_value*) 读取 DAC 原始码值
pynect_direct_set_level_output(level) 设置数字电平输出位掩码
pynect_direct_get_level_output(level*) 读取电平输出位掩码

9.6 外触发采集

函数 说明
pynect_direct_set_external_trigger_config(enable, trig_type, scan_num) 设置外触发开关、类型(上升沿 0xAA/电平 0xBB)和计划帧数
pynect_direct_get_external_trigger_config(enable*, trig_type*, scan_num*) 读取外触发配置
pynect_direct_get_spectrum_capture_status(status*) 读取外触发采集 64 位原始状态字
pynect_direct_external_spectrum_control(frame_index, data_out, max_len) 读取指定外触发帧的强度;成功返回写入字节数

9.7 Flash 与设备复位(高风险)

函数 说明
pynect_direct_set_flash_write_protect() 发送设备定义写保护命令(无参数)
pynect_direct_get_flash_write_protect(enabled*) 读取写保护状态标记(1/0)
pynect_direct_reset_device() 复位设备;会使临时参数和选定波段失效
pynect_direct_flash_read(sector, data_out, max_len) 读取指定扇区原始字节;成功返回实际字节数
pynect_direct_flash_write(sector, data, len) 写入指定扇区;错误数据可能破坏标定,谨慎使用

9.8 固化配置与狭缝(非易失)

函数 说明
pynect_direct_set_stored_integration_time(us) 写入默认积分时间(影响以后上电默认值)
pynect_direct_set_stored_average_number(count) 写入默认平均次数
pynect_direct_set_stored_smoothing_width(width) 写入默认平滑宽度
pynect_direct_get_stored_integration_time(us*) 读取固化默认积分时间
pynect_direct_get_stored_average_number(count*) 读取固化默认平均次数
pynect_direct_get_stored_smoothing_width(width*) 读取固化默认平滑宽度
pynect_direct_get_slit_width(um*) 读取入射狭缝宽度信息

10. 典型应用流程

10.1 单次采集并显示(最常见)

流程:扫描 → 打开 → 读点数 → 读波长 → 采光谱 → 显示/保存 → 关闭。

Python 最小示例(类型绑定见 8.2 节 bind_api;此处省略):

import ctypes
from minimal_test import find_dll, load_library, bind_api  # 复用已验证绑定

sdk = load_library(find_dll())
bind_api(sdk)

require(sdk.pynect_direct_open_device(0), "open")          # 1. 打开设备 0

count = ctypes.c_uint16()
require(sdk.pynect_direct_get_wavelength_count(ctypes.byref(count)), "count")
n = count.value                                           # 2. 数据点数 N

wl = (ctypes.c_double * n)()
require(sdk.pynect_direct_get_full_wavelength_data(wl, n), "wavelength")

sdk.pynect_direct_set_integration_time(50000)             # 3. 积分时间 50 ms
spec = (ctypes.c_uint16 * n)()
require(sdk.pynect_direct_get_full_spectrum_data(spec, n), "spectrum")

for i in range(0, n, max(1, n // 10)):                    # 4. 均匀取 10 点显示
    print(f"{wl[i]:8.3f} nm  {spec[i]}")
print(f"... 共 {n} 点,范围 {wl[0]:.2f} ~ {wl[-1]:.2f} nm")

sdk.pynect_direct_close_device()                          # 5. 关闭设备

C 语言完整版见第 5.2 节(spectrum_demo.c),仅把其中"读信息/选区"部分去掉即为最小单次采集流程。

10.2 指定波段显示

set_selected_wavelength_range(400, 800)
get_selected_wavelength_range(&sel)          // 得到 sel.count
get_selected_wavelength_data(wl_buf, sel.count)
get_selected_spectrum_data(spec_buf, sel.count)

切换波段只是重新调用 set_selected_wavelength_range,设备无感知,速度很快。

10.3 多设备管理(同时打开两台设备)

/* 1. 扫描设备,确认芯片序列号 */
int n = pynect_direct_scan_devices();
for (int i = 0; i < n; i++) {
    char chip[64];
    if (pynect_direct_get_chip_serial_by_index(i, chip, sizeof(chip)) == 0)
        printf("device %d: %s\n", i, chip);
}

/* 2. 分别用芯片序列号打开两台设备(互不影响,均保持打开) */
pynect_direct_open_device_by_chip_serial("FTXXXX1");
pynect_direct_open_device_by_chip_serial("FTXXXX2");
printf("open devices: %d\n", pynect_direct_get_open_device_count());  // 2

/* 3. 切换当前设备并采集(每台设备状态独立) */
pynect_direct_select_device(0);   /* 当前:第一台 */
uint16_t count;
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec0, count);

pynect_direct_select_device(1);   /* 当前:第二台 */
pynect_direct_get_wavelength_count(&count);
pynect_direct_get_full_spectrum_data(spec1, count);

/* 4. 结束:全部关闭或逐个关闭 */
pynect_direct_close_all_devices();

10.4 数据保存

wl[](double)与 spec[](uint16)两列写入 CSV 即可(示例见 Python 手册 python_ctypes_example_guide.md)。


11. 常见问题(FAQ)

Q1:DLL 加载失败,提示找不到 PynectDirect.dll?
PynectDirect.dllftd2xx.dll 必须同目录;检查路径和位数(必须 64 位进程)。

Q2:Python 调用返回奇怪的结果?
忘记设置 argtypes/restype 了。所有 Python 调用必须先绑定类型(见 8.2 节 bind_api)。

Q3:scan_devices 返回 0?
设备未连接、驱动未装、或设备被其他程序占用。插拔设备后重试。

Q4:打开设备返回 -6?
设备被其他进程独占(如另一个采集软件)。关闭其他程序后重试。

Q5:get_selected_* 返回 -1?
尚未调用 set_selected_wavelength_range,或设备复位后窗口失效。先设置选区。

Q6:强度全为 0 或全部饱和?
积分时间过短或过长。调整 set_integration_time;确认光源/氙灯工作正常。

Q7:同一台设备能否两个程序同时访问?
不能。同一台物理设备只能被一个进程打开,第二个进程打开会失败(-6)。但同一个程序内可以同时打开多台不同设备(最多 4 台),用 select_device 切换操作。

Q7b:同时打开多台设备后,参数会互相影响吗?
不会。每台设备有独立的协议、能力、积分时间、平均次数和选定波段状态;切换设备后读写的是该设备自己的状态。

Q8:设置的积分时间关机后还在吗?
不在。set_integration_time 只影响当前会话;要持久保存请使用 set_stored_integration_time(会改变设备默认值,谨慎)。

Q9:32 位程序能调用吗?
不能。SDK 是 64 位 DLL。

Q10:C# 调用时返回乱码或崩溃?
检查 P/Invoke 是否使用 CallingConvention.Cdecl,结构体字段顺序/大小是否与头文件一致,编译目标是否为 x64。


12. 错误码与故障排查

12.1 错误码速查

返回码 含义 处理建议
0 成功 -
-1 未初始化(选区未设置) 先调用 set_selected_wavelength_range
-2 设备未找到/索引越界 重新扫描;检查设备连接
-3 设备未打开 先调用 open_device
-4 通信失败 检查线缆;重新插拔设备
-5 参数无效 检查索引、范围、数组容量、字符串缓冲区
-6 打开失败(被占用) 关闭其他占用程序
-7 CRC 校验失败 通信干扰,重试;换线缆
-8 帧头不匹配 协议识别异常;尝试强制指定协议
-9 超时 检查设备响应;重新上电
-10 写保护 检查 Flash 写保护状态
-11 不支持 先查 get_capabilities 确认功能位

12.2 排查顺序

  1. 确认 bin/ 目录有且仅有 PynectDirect.dll + ftd2xx.dll(例程目录内同样各有一份)。
  2. 确认 Python/程序是 64 位。
  3. 运行 examples/python/minimal_test.py --read-only,看哪一步 FAIL。
  4. 确认没有其他软件占用设备。
  5. 插拔设备 USB 后重试。

附录:交付包结构

PynectDirect_SDK/
|-- README.md
|-- bin/
|   |-- PynectDirect.dll      # SDK 动态库
|   `-- ftd2xx.dll            # FTDI 驱动(必需)
|-- include/
|   `-- pynect_direct.h       # 公开头文件(签名权威来源)
|-- lib/
|   |-- libPynectDirect.dll.a # GCC 导入库
|   `-- libPynectDirect.a     # GCC 静态库
|-- examples/                  # 每个例程一个独立目录,依赖文件均已放入
|   |-- c/                      # C 例程:源码 + include + lib + 两个 DLL
|   |   |-- spectrum_demo.c
|   |   |-- include/pynect_direct.h
|   |   |-- lib/libPynectDirect.dll.a, libPynectDirect.a
|   |   `-- PynectDirect.dll, ftd2xx.dll
|   |-- cpp/                    # C++ 例程(结构同 c/)
|   |-- csharp/                 # C# 例程:spectrum_demo.cs + 两个 DLL
|   `-- python/                 # Python 例程:全部 py 脚本 + 两个 DLL
|       |-- spectrum_demo.py            # 单次采集(第 8 章)
|       |-- minimal_test.py             # 设备冒烟测试
|       |-- python_ctypes_example.py    # 交互式指定波段示例 + 指南
|       `-- dual_spectrometer_test.py   # 双光纤光谱仪(UV/VIS + NIR)同时采集测试
`-- docs/
    |-- PynectDirect_API_Guide.md
    `-- PynectDirect用户使用手册v3.0.1.md  # 本文档