ZLinear DABM-D223 通信协议与软件开发全解析:从 USB CDC 到 DDS 波形输出

www.z-linear.com


一、通信架构总览

D223 的通信链路可以概括为:

复制代码
┌──────────────────────────────────────────────────┐
│                    上位机 (PC)                     │
│                 USB CDC 虚拟串口                    │
└──────────────────────┬───────────────────────────┘
                       │  0x55AA 协议帧 + CRC16
┌──────────────────────┴───────────────────────────┐
│                STM32H743 (主控)                     │
│  ┌──────────┐  ┌───────────┐  ┌────────────────┐  │
│  │ RT-Thread │  │ USB消息处理 │  │  ADC功能线程    │  │
│  │  调度器    │  │ (优先级1)   │  │ (优先级0,最高)  │  │
│  └──────────┘  └───────────┘  └────────────────┘  │
│                                                   │
│  TIM12(USB超时)  TIM13(ADC采集)  TIM14(DAC输出)    │
└──────────────────────┬───────────────────────────┘
                       │  SPI/QSPI
┌──────────────────────┴───────────────────────────┐
│              FPGA (GW1NR-LV4QN88C6)                │
│         信号路由 / ADC数据中转 / DI状态缓存          │
└──────────────────────────────────────────────────┘

D223 采用 STM32H743 + FPGA 双芯片架构,与 7606 的单 STM32 架构有本质区别 note_63。所有外部信号(ADC/DI/DO)都经过 FPGA 中转,再通过 SPI/QSPI 传递给 STM32,这决定了 D223 在通信和数据处理上的独特方式。


二、USB CDC 虚拟串口通信

2.1 物理层:USB 2.0 HS + CH340 驱动

D223 通过 USB 2.0 HS 接口 与 PC 通信,使用 USB CDC 虚拟串口 协议 word_8。在 PC 端需要安装 CH340 驱动,安装后系统会枚举出一个虚拟 COM 口(如 COM3、COM16 等),上位机软件通过标准的串口 API 即可收发数据 word_8

💡 提示 :CH340 驱动安装包可在百度网盘的开源资料目录中找到,路径为 000.开发软件安装包/5.CH340驱动(USB串口驱动) word_8

2.2 串口参数

D223 使用 USB CDC 虚拟串口,串口参数对实际传输无影响(USB 总线不受波特率限制),但上位机软件通常配置为:

参数
波特率 115200(仅形式参数)
数据位 8
停止位 1
校验位 None

从手册提供的 XCOM 调试截图可以看到,实际通信测试使用的就是上述配置 word_8


三、自定义通信协议详解

3.1 帧格式

D223 使用 ZLinear 统一的自定义协议,帧格式如下 word_7word_8

复制代码
┌──────┬───────┬──────────┬─────────────┬──────┐
│ Head │ CmdId │ FrameLen │    Data     │ CRC  │
│ 2B   │  1B   │   2B     │   N Bytes   │ 2B   │
│0x55AA│       │          │             │      │
└──────┴───────┴──────────┴─────────────┴──────┘
字段 长度 说明
Head 2 字节 帧头,固定为 0x55AA
CmdId 1 字节 命令 ID,标识指令类型
FrameLen 2 字节 帧总长度(含 CRC),大端序
Data N 字节 数据负载,长度可变
CRC 2 字节 CRC16 校验,覆盖从帧头到 CRC 前的所有数据

⚠️ 注意大小端FrameLenCRC 采用 大端序(Big-Endian) ,代码中使用 SWAP16() 函数进行大小端转换 word_8。而 7606 代码分析文档中也提到"所有多字节字段采用小端序 (Little-Endian)" word_4,这里需要特别注意------不同型号可能存在差异,开发时务必以目标型号的实际代码为准。

3.2 msgHead 结构体

协议头部在 C# 上位机和 C 固件中都通过结构体映射,保证跨平台一致 word_7

csharp 复制代码
// C# 上位机 (msgStruct.cs)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct msgHead
{
    public UInt16 head;      // 帧头: 0x55AA
    public byte cmdId;       // 命令ID
    public UInt16 frameLen;  // 帧长度(含CRC)
}
c 复制代码
// C 固件 (msgStruct.h)
typedef struct {
    u16 msgHead;    // 包头 0x55AA
    u8  cmdId;      // 命令ID
    u16 frameLen;   // 包长度(含CRC)
} msgHead;

3.3 命令 ID 分类

根据 7606 上位机代码分析,命令 ID 按功能范围划分如下 word_3word_7

范围 命令类别 典型命令
0x01-0x07 ADC 基础控制 通道使能、设备参数、波形数据
0x08-0x1A 数据记录 Flash记录、SRAM记录、在线记录、连续读取
0x20-0x28 DDS/DAC 控制 参数获取/设置、工作模式、校准
0x30-0x34 IO 控制 IO状态读取、输出设置、PWM控制
0x40-0x42 ADC 高级设置 量程、信号类型、自动触发
0x50-0x5F 系统设置 校准参数、IP/端口、设备地址
0xF0/0x80 固件版本 软件版本号获取

D223 的命令 ID 与 7606 基本一致,均遵循同一协议体系 word_3word_7

3.4 常用消息结构体

协议定义了一套层次化的消息结构体体系 word_3

结构体名 用途 字段
msgHead 公共帧头 head + cmdId + frameLen
msgNoParam 无参数命令 head + cmdId + frameLen + ch + crc
msgUbyte 单字节参数 + data(1B)
msgUWord 双字节参数 + data(2B)
msgGetAdcDatas ADC 波形数据 + dataLen + chEnable + signalType + range + startCh + trigCh + samplingRate + trigValue + adcData4000
msgGetDioAll 全部IO状态 + din8 + dout4 + pwmEnable4 + pwmFreq4 + pwmDuty4
msgDdsDacParam DDS参数 + ch + samplingRate + freq + volt + duty + bias + gain

四、实战:单点 ADC 读取协议解析

4.1 发送命令

以单点记录仪(同时读取 8 通道 ADC 和 8 通道 DI 状态)为例,发送命令为 word_8

复制代码
55 AA 08 00 08 00 11 A6
字节 含义
55 AA 0x55AA 帧头
08 0x08 cmdId = 单点读取
00 08 0x0008 帧长度 = 8 字节(大端序)
00 0x00 通道号(未使用,默认0)
11 A6 0x11A6 CRC16 校验值

4.2 接收返回数据

采集卡返回 43 字节数据 word_8

复制代码
55 AA 08 00 2B 03 00 00 00 00 81 64 C0 00 81 64 BE 
00 81 5F 41 00 81 61 A4 00 81 58 45 00 81 59 11 
00 81 63 AC 00 81 64 14 0D E6
偏移 含义
0-1 55 AA 帧头
2 08 cmdId
3-4 00 2B 帧长度 = 43 字节
5 03 ADC 通道使能状态(bit0~bit7 对应 CH1~CH8)
6 00 信号类型(0=电压,1=电流)
7 00 ADC 量程(0=±5V,1=±10V)
8 00 8通道 DI 状态(bit0~bit7 对应 DIN1~DIN8)
9-12 00 81 64 C0 通道1 ADC 值(24位,大端序)
13-16 ... 通道2~8 ADC 值
41-42 0D E6 CRC16 校验值

4.3 ADC 值换算

ADC 原始值为 24 位有符号数,换算公式为 word_8

复制代码
电压值 = (rawValue - 0x800000) / 0x800000 × 量程

其中:
  0x800000 = 8388608 (24位中位值,对应0V)
  0x7FFFFF = 8388607 (最大正量程)
  0x000000 = 0       (最大负量程)

以通道1为例:0x008164C0 = 8484800

复制代码
电压 = (8484800 - 8388608) / 8388608 × 5 = 0.010V × (5V量程)

💡 在线记录模式 下,ADC 数据连续上传,发送 55 AA 16 00 0C FF 00 00 4E 20 44 77 后采集卡会持续返回数据直到手动停止 word_8。其中 0x4E20 = 20000,即 20K 采样率。


五、上位机接收状态机解析

5.1 核心流程

上位机 C# 代码中,串口数据接收采用 100ms 定时器 + 缓冲区扫描 的状态机模式 word_7word_8

复制代码
SerialPort.DataReceived 事件
         │
         ▼
┌───────────────────────┐
│  Com_DataReceived()    │  串口中断,读取原始字节流
│  追加到 usbRxMsgBuf    │  List<byte> 动态缓冲区
└──────────┬────────────┘
           │
           ▼  (100ms 定时器 rxTim_Tick 触发)
┌───────────────────────┐
│  ① 搜索帧头 0x55AA     │  遍历缓冲区查找帧头
│  ② 判断帧长度是否完整   │  剩余数据 ≥ frameLen?
│  ③ CRC16 校验          │  checkCrc16WithTail()
│  ④ 分发到业务层         │  触发 _deviceRxMsg 事件
│  ⑤ 移除已处理帧         │  RemoveRange(0, frameLen)
└───────────────────────┘

5.2 关键代码逻辑

csharp 复制代码
private void rxTim_Tick(object sender, EventArgs e)
{
    // 1. 缓冲区数据不足5字节,直接返回
    while (usbRxMsgBuf.Count > 5)
    {
        byte[] usbRxBuf = usbRxMsgBuf.ToArray();
        bool findNewHead = false;

        // 2. 搜索帧头 0x55 0xAA
        for (UInt16 i = 0; i < usbRxBuf.Length; i++)
        {
            if (usbRxBuf[i] == 0x55 && i != (usbRxBuf.Length - 1))
            {
                if (usbRxBuf[i + 1] == 0xAA)
                {
                    // 清除帧头前的脏数据
                    usbRxMsgBuf.RemoveRange(0, i);
                    findNewHead = true;
                    break;
                }
            }
        }

        // 3. 转换为协议头结构体,读取帧长度
        _pRxHead = (msgHead)msgStruct.BytesToDataStruct(usbRxBuf, typeof(msgHead));

        // 4. 判断数据是否足够一帧
        if ((usbRxBuf.Length) >= SWAP16(_pRxHead.frameLen))
        {
            // 5. CRC16 校验
            string crcResult = crcCheck.checkCrc16WithTail(usbRxBuf, SWAP16(_pRxHead.frameLen));
            if (crcResult != "OK")
            {
                // 校验失败:删除错误的帧头,重新搜索
                usbRxMsgBuf.RemoveRange(0, 2);
                break;
            }

            // 6. 校验通过:抛给业务层处理
            _deviceRxMsg(usbRxMsgBuf.ToArray());

            // 7. 删除已处理的一帧
            usbRxMsgBuf.RemoveRange(0, SWAP16(_pRxHead.frameLen));
            break;
        }
        else
        {
            // 数据不够一帧,等待下次接收
            return;
        }
    }

    // 遍历完都没找到帧头,清空缓冲区
    if (!findNewHead)
    {
        usbRxMsgBuf.Clear();
    }
}

5.3 观察者模式消息分发

上位机采用 观察者模式 进行消息分发。主窗体定义事件委托,各功能子页面通过订阅接收数据 word_7

csharp 复制代码
// Form1.cs - 主窗体定义事件
public delegate void deviceRxMsg(byte[] data);
public event deviceRxMsg _deviceRxMsg;

// 各子页面订阅消息事件
this._mainForm._deviceRxMsg += new deviceRxMsg(rxMsgProcess);
页面 切换时自动请求
波形显示 _cmdId_getDeviceParam
数据记录 _cmdId_getDeviceParam
DDS 控制 _cmdId_ddsDacGetParamTotal
DIO 控制 _cmdId_getIoAll
系统设置 _cmdId_getSettingParam

六、CRC16 校验实现

6.1 固件端(C 语言)

D223 固件使用 CRC16-Modbus 算法,采用 查表法 实现 note_63

c 复制代码
u16 getCrc16WithTail(unsigned char *puchMsg, unsigned short usDataLen)
{
    u8 crcL = 0xFF, crcH = 0xFF;  // CRC初始值 0xFFFF
    usDataLen -= 2;                // 减去尾部2字节CRC本身

    while(usDataLen--)
    {
        uIndex = crcH ^ *puchMsg++;
        crcH = crcL ^ tableH[uIndex];  // 查表法高字节
        crcL = tableL[uIndex];          // 查表法低字节
    }
    return (crcH << 8 | crcL);
}

6.2 CRC16-Modbus 算法参数

参数
多项式 0xA001(0x8005 反转)
初始值 0xFFFF
输入反转
输出反转
异或输出 0x0000

该算法与 Modbus RTU 标准完全一致 pdf_127

6.3 固件端消息处理

固件端收到数据后,先验证帧头和 CRC,再按 cmdId 分发 note_63

c 复制代码
void fun_msgProcess_task(u8 port, u8* rxData)
{
    msgHead *_pRx = (msgHead*)rxData;

    // 1. 帧头验证
    if(_pRx->msgHead != SWAP16(0x55AA))
        return;

    // 2. CRC16 校验
    if(!checkCrc16WithTail((u8*)rxData, SWAP16(_pRx->frameLen)))
        return;

    // 3. 记录通信端口
    global_hostCommunicationPort = port;

    // 4. 命令分发
    switch(_pRx->cmdId)
    {
        case _cmdId_getIoAll:        fun_msgGetIoAll();          break;
        case _cmdId_getAdcValue:     fun_msgGetAdcValue();       break;
        case _cmdId_setDdsDacParam:  fun_msgSetDdsDacParam(rxData); break;
        // ... 更多命令
    }
}

七、RT-Thread 多线程架构

7.1 线程模型

D223 固件基于 RT-Thread RTOS,核心线程如下 note_63

线程 创建函数 优先级 职责
ADC 功能线程 rt_adcFunc_init() 0(最高) ADC 采集、双缓冲管理、数据上传
USB 消息处理线程 rt_msgProcess_init() 1 接收上位机指令、命令分发、回复数据
主循环 while(1) --- 动态更新采样率、读取 FPGA 寄存器

7.2 定时器分工

定时器 时钟源 功能
TIM12 240MHz USB 接收超时检测(1ms 周期)
TIM13 240MHz ADC 数据采集触发
TIM14 1MHz DAC 波形输出触发

7.3 系统时钟树

D223 使用 STM32H743,时钟配置如下 note_63

复制代码
HSE 25MHz → PLLM=5 → 5MHz → PLLN=192 → 960MHz (VCO)
                                        ├── PLLP=2 → 480MHz (SYSCLK)
                                        └── PLLQ=20 → 48MHz (USB)

SYSCLK 480MHz → HCLK 240MHz (DIV2)
               → APB1 120MHz (DIV2)
               → APB2 120MHz (DIV2)

TIM 时钟 = 240MHz (APB1 定时器时钟)

TIM13 使用 240MHz 时钟计算采样率:ARR = 240000000 / samplingRate - 1 note_63


八、ADC 双缓冲机制

D223 在在线记录模式下采用 AB 双缓冲 机制,避免数据丢失 note_63

复制代码
                    ┌─────────────────┐
  FPGA FIFO ──────→ │  缓冲区 A        │ ──→ USB 上传
                    │  缓冲区 B        │ ──→ USB 上传
                    └─────────────────┘

  工作流程:
  1. 数据写入缓冲区A,A满后切换到B
  2. 同时上传缓冲区A的数据
  3. B满后切换回A,同时上传B的数据
  4. 循环往复...

关键代码逻辑:

c 复制代码
// 双缓冲标志: 0=A缓冲, 1=B缓冲
_uadc.recordAB_flag

// A缓冲满 → 标记flag=2, 切换到B
// B缓冲满 → 标记flag=3, 切换回A

// 数据写入(大端转换)
_adcBuf.data[_uadc.adc_collectIndex++] = SWAP16(ADC数据);

FPGA FIFO 读取采用 QSPI DMA 方式,每次读取 66 字节(包含索引和 ADC 数据),通过判断 FIFO 写索引差值来计算新数据量 note_63


九、DDS 波形输出详解

9.1 工作模式

D223 的 4 通道 DAC 支持 6 种工作模式 note_63

模式 枚举值 说明
停止输出 0x00 DAC 输出 0V
直流输出 0x01 输出固定电压
正弦波 0x02 500 点查找表 + 相位累加
三角波 0x03 线性上升 → 线性下降
方波 0x04 占空比可调
自定义波形 0x05 从数组循环查表

9.2 DDS 计算核心

正弦波使用 500 点查找表 + 相位累加器 实现 note_63

c 复制代码
// 相位累加
_ddsDac.dacIndex[i] += freq * 500000 / interruptFreq;
_ddsDac.dacIndex[i] %= 500000;  // 模500000(500点 × 1000精度)

// 查表索引
dacIndexInt = _ddsDac.dacIndex[i] / 1000;

// 查表 + 幅度 + 偏置
_ddsDac.dacCCR[i] = sinTable[dacIndexInt] * amp / 65536 + bias;

多通道相位同步 :同频通道共享通道 0 的相位累加器,仅叠加相位偏移 phase * 500000 / 360,实现多通道相位同步输出 note_63

9.3 DAC 硬件输出

DAC 通过 SPI2 逐通道写入 DAC 芯片,由 TIM14 中断驱动 note_63

c 复制代码
void ddsDac_tim_task()
{
    if(任意通道使能)
    {
        ddsDacCalc();  // 计算所有通道的CCR值

        // 通道1: 寄存器地址 0x08
        DA_CS(0);
        dac_txBuf[0] = 0x08;
        dac_txBuf[1] = _ddsDac.dacCCR[0] >> 8;   // 高8位
        dac_txBuf[2] = _ddsDac.dacCCR[0] & 0xFF;  // 低8位
        HAL_SPI_Transmit(&hspi2, dac_txBuf, 3, 1);
        DA_CS(1);

        // 通道2: 0x0a, 通道3: 0x0c, 通道4: 0x0e 同理
    }
}

9.4 DAC 输出率动态调整

TIM14 基于 1MHz 时钟,DAC 输出率可通过 FRAM 参数动态调整 note_63

c 复制代码
u32 dacFreq = _framDatas.dacOutRate;               // 目标输出率
htim14.Instance->ARR = 1000000 / dacFreq - 1;     // 设置定时器重载值

十、与 7606 通信能力对比

D223 和 7606 虽然使用相同的协议体系,但通信通道有显著差异 word_3

特性 D223 7606
主通信 USB 2.0 HS(CDC 虚拟串口) USB Type-C(CDC 虚拟串口)
RS485 USART2(PD4/PD5/PD6,经 FPGA) USART1(PA9/PA10,SP3485 收发器)
以太网 ❌ 无 ✅ W5100S 硬件 TCP/IP
Modbus RTU ❌ 无独立串口 ✅ 独立 RS485 串口
Modbus TCP ❌ 无 ✅ 以太网 TCP
FPGA 中转 ✅ 有 ❌ 无
帧协议 0x55AA + CRC16 0x55AA + CRC16(相同)

7606 支持 三套独立通信通道,三通道对比 word_3

特性 USB 自定义协议 Modbus RTU Modbus TCP
物理层 USB 虚拟串口 RS485 串口 以太网 TCP
帧头 0x55AA 设备地址 MBAP 头
校验 CRC16(帧尾) CRC16(帧尾) 无(TCP 保证)
数据量 大(波形数据) 小(寄存器) 小(寄存器)
用途 波形 + 控制 工业控制 工业控制
独立串口 主串口 独立串口 Socket

D223 仅保留 USB 通道,定位为 中速采集 + DDS 信号发生,面向不需要工业总线但对采样率有要求的场景。


十一、开发资源与工具链

11.1 开源资料清单

D223 的全部开源资料存放在百度网盘 word_8

资料包 内容
DABM-D223上位机直接运行版.7z zlTool 免安装上位机,无需安装任何软件即可使用
C#_visualStudio_DABM_D223.7z C# 上位机源码(Visual Studio 2022)
STM32 DABM D223.7z STM32 固件源码(Keil MDK 5 + RT-Thread)
FPGA DABM_D223.7z FPGA 工程文件(高云半导体)
DABM-D223.pdf 原理图(PDF 格式)

11.2 开发工具链

工具 用途 版本
Keil MDK 5 STM32 固件开发 最新版
STM32CubeMX STM32 配置工具 最新版
RT-Thread 实时操作系统 支持包
Visual Studio 2022 C# 上位机开发 社区版
QT 5.14.2 QT 上位机开发 ---
LabVIEW 2024 LabVIEW 开发 Q1 (32位)
高云 FPGA IDE FPGA 开发 ---
Source Insight 代码阅读 ---
CH340 驱动 USB 虚拟串口驱动 ---

⚠️ LabVIEW 说明 :手册中列出 LabVIEW 为支持的开发环境,网盘也提供了 LabVIEW 2024 安装包,但 未提供 LabVIEW VI 例程文件。如需在 LabVIEW 中使用 D223,需自行通过 VISA 串口控件实现 0x55AA 协议通信 word_8

11.3 快速上手建议

  1. 安装 CH340 驱动 → 连接 D223 → 确认设备管理器中出现 COM 口
  2. 运行 zlTool → 选择对应 COM 口 → 点击连接 → 验证通信正常
  3. 阅读 C# 源码 → 重点看 msgStruct.cs(协议定义)和 Form1.cs(接收状态机)
  4. 二次开发 → 参考 msgStruct 结构体体系,实现自己的协议解析逻辑

十二、总结

核心要点

要点 说明
通信方式 USB 2.0 HS + CDC 虚拟串口 + CH340 驱动
协议格式 0x55AA + cmdId + frameLen + data + CRC16,大端序
CRC 算法 CRC16-Modbus,多项式 0xA001,初始值 0xFFFF
上位机架构 观察者模式 + 100ms 定时器状态机 + 事件驱动
固件架构 RT-Thread RTOS + AB 双缓冲 + FPGA 中转
DDS 输出 500 点查表 + 相位累加器,支持正弦/三角/方波/自定义
开发资源 C# 源码 + STM32 固件 + FPGA 工程 + 免安装上位机

开发避坑指南

  1. 大小端问题 :协议中 FrameLen 和 CRC 是大端序,代码中频繁使用 SWAP16() 转换,开发时务必注意
  2. CRC 校验范围:CRC 覆盖从帧头到 CRC 前的所有字节,计算时需排除尾部 2 字节 CRC 本身
  3. 100ms 定时器:上位机每 100ms 检查一次缓冲区,高频数据流可能需要调整定时器周期
  4. FPGA 中转延迟:D223 的 DI/DO 经过 FPGA,与 7606 直连 GPIO 相比存在额外延迟
  5. DAC 输出范围:0-5V,需要更高电压时需外接运放
  6. LabVIEW 无例程:需自行通过 VISA 串口实现协议通信

参考资料

  • DABM-D223 用户手册(通信协议、命令示例、上位机代码解析)word_8
  • DABM-D223 代码解析(GPIO 定义、RT-Thread 线程、DDS/DAC、CRC16、双缓冲、时钟树)note_63
  • 7606 上位机代码分析1(帧格式、msgHead 结构体、命令 ID、接收状态机)word_7
  • 7606 上位机代码分析2(命令 ID 枚举、三套通信协议、数据流处理)word_3
  • 7606 代码分析(固件架构、USB 消息分发、命令 ID 列表)word_4
  • MODBUS 软件开发实战指南(CRC16-Modbus 算法原理与实现)pdf_127

本文基于 ZLinear 官方文档和开源代码进行分析。如有出入,请以厂家最新文档和源码为准。

如果觉得有用,欢迎点赞收藏!有问题欢迎评论区交流~

相关推荐
microrain1 小时前
从监控孤岛到视联一体:SagooIoT视频监控中心的设计实践
物联网·golang·开源·sagooiot
tedcloud1231 小时前
Impeccable 部署指南:开源前端设计工具 Linux 环境搭建实践
linux·运维·服务器·前端·人工智能·开源
小比特-combat1 小时前
多模式调光台灯
单片机·嵌入式硬件
2CM_Embed2 小时前
Altium Designer 中为什么一颗芯片会分成多个 Part?——以 C2000 MCU 为例
单片机·嵌入式硬件·altium designer·c2000
大爱编程♡3 小时前
STM32-USART串口
stm32·单片机·嵌入式硬件
hongmai6668884 小时前
耐高温小钢炮:ESP32-C3-MINI-1U-H4模组选型分享
笔记·单片机·嵌入式硬件·物联网·risc-v
yunwei374 小时前
eBPF 教程:精准隔离已建立的 TCP 连接
linux·安全·开源
zyplayer-doc4 小时前
同一份制度别复制到多个知识库:用zyplayer-doc引用文档解决重复维护
javascript·人工智能·智能手机·开源·ocr
qq_573967825 小时前
STM32问题
stm32·单片机·嵌入式硬件