D223上位机C#开发实战:从帧解析到波形显示的完整实现

拿到D223的开源上位机源码后如何理解和修改?本文从C#代码层面,解析上位机的帧接收解析、CRC校验、波形绘制和校准界面实现。


一、上位机架构总览

D223的C#上位机基于WinForms开发,使用Visual Studio 2022社区版word_3word_10。核心代码结构word_3word_7

复制代码
zlTool/
├── msgStruct.cs       # 消息协议:帧头定义、CRC校验、命令枚举、校准算法
├── Setting.cs         # 设置页面:量程切换、24位数据转换、校准参数
├── WaveForm.cs        # 波形页面:数据接收、解析、绘制
├── rxTim定时器        # 100ms接收定时器:帧查找、校验、分发
└── Program.cs         # 主程序入口

二、命令ID分类体系

D223上位机与采集卡之间的命令按功能分区word_3

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

这套命令分区体系覆盖了D223的全部功能,共30+条命令word_3


三、帧接收与解析

3.1 100ms接收定时器

上位机使用100ms定时器rxTim检查接收缓冲区word_6

复制代码
每100ms执行一次:
1. 遍历查找帧头 0x55 0xAA
2. 找到帧头后,读取帧长度
3. 判断缓冲区剩余数据 ≥ 一帧完整长度?
   - 不够 → 等下一次100ms
   - 够 → 继续
4. CRC16校验
   - 错误 → 删除错误帧头(0x55AA),重新查找
   - 正确 → 交给业务层处理
5. 删除已处理的一帧数据
6. 如果遍历完都没找到0x55AA → 清空缓冲区

3.2 帧结构定义

所有消息统一格式word_6note_63

csharp 复制代码
// 帧结构
[帧头: 0x55AA] [命令ID: 1B] [帧长度: 2B] [数据段: NB] [CRC16: 2B]

C#结构体定义word_3

csharp 复制代码
public struct msgHead {
    public ushort msgHead;    // 0x55AA(大端)
    public byte cmdId;        // 命令ID
    public ushort frameLen;   // 帧总长度(大端)
}

3.3 大小端处理

STM32是小端,通信协议用大端word_3

csharp 复制代码
// 大小端转换宏
#define SWAP16(x)  ((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF))
#define SWAP32(x)  ((((x) & 0xFF) << 24) | (((x) & 0xFF00) << 8) | \
                    (((x) >> 8) & 0xFF00) | (((x) >> 24) & 0xFF))

帧头在内存中存储为0xAA55,但发送到USB线上是0x55 0xAAnote_63

3.4 CRC16校验

上位机使用CRC16-Modbus算法word_6note_63

csharp 复制代码
public static ushort getCrc16WithTail(byte[] data, int len) {
    byte crcL = 0xFF, crcH = 0xFF;
    len -= 2;  // 减去CRC本身2字节
    
    while(len-- > 0) {
        int index = crcH ^ data[dataOffset++];
        crcH = (byte)(crcL ^ tableH[index]);
        crcL = tableL[index];
    }
    return (ushort)((crcH << 8) | crcL);
}

CRC16-Modbus参数:

  • 初始值:0xFFFF
  • 多项式:0xA001
  • 查表法加速,预计算256个值的tableH和tableLpdf_127

四、波形数据接收与显示

4.1 波形数据包结构

采集卡上传的波形数据包结构word_5

csharp 复制代码
struct adcWaveBuf {
    ushort msgHead;          // 0x55AA
    byte cmdId;              // 波形数据命令ID
    ushort frameLen;         // 帧长度
    byte adcStartCh;         // 起始通道号
    byte adc_ch_Enable;      // 通道使能位掩码
    byte trigCh;             // 触发通道
    ushort trigValue;        // 触发值
    ushort samplingRate;     // 采样率
    byte adcRange;           // 量程
    byte adcSignalType;      // 信号类型
    short[] data;            // ADC波形数据
    ushort crc;              // CRC16
}

4.2 数据接收解析

上位机接收波形数据的处理流程word_5

csharp 复制代码
void OnWaveDataReceived(byte[] data) {
    // 解析数据包
    ushort msgHead = (data[0] << 8) | data[1];  // 0x55AA
    byte cmdId = data[2];
    ushort frameLen = (data[3] << 8) | data[4];
    
    // 提取波形参数
    byte adcStartCh = data[5];
    byte adcChEnable = data[6];
    byte trigCh = data[7];
    ushort trigValue = (data[8] << 8) | data[9];
    ushort samplingRate = (data[10] << 8) | data[11];
    
    // 提取ADC数据
    int dataOffset = 12;
    int dataLen = frameLen - dataOffset - 2;  // 减去CRC
    short[] waveData = new short[dataLen / 2];
    for(int i = 0; i < dataLen / 2; i++) {
        waveData[i] = (short)((data[dataOffset + i*2] << 8) | data[dataOffset + i*2 + 1]);
    }
    
    // 显示波形
    DrawWaveform(waveData, samplingRate, trigValue);
}

4.3 波形绘制

波形绘制的核心代码word_5

csharp 复制代码
void DrawWaveform(short[] data, int samplingRate, int trigValue) {
    // 计算时间轴
    float timeStep = 1.0f / samplingRate;  // 每点的时间间隔
    
    // 绘制网格
    DrawGrid();
    
    // 绘制波形
    for(int i = 0; i < data.Length - 1; i++) {
        float x1 = i * timeStep;
        float y1 = AdcToVoltage(data[i]);  // ADC值转电压
        float x2 = (i + 1) * timeStep;
        float y2 = AdcToVoltage(data[i + 1]);
        DrawLine(x1, y1, x2, y2);
    }
    
    // 标记触发点
    MarkTriggerPoint(trigValue);
    
    // 显示测量参数
    DisplayMeasurements(data);
}

4.4 ADC值转电压

csharp 复制代码
float AdcToVoltage(short adcValue) {
    // 根据量程转换
    if(adcRange == 1)  // ±5V
        return (adcValue - 32768) / 32768.0f * 5.0f;
    else                // ±10V
        return (adcValue - 32768) / 32768.0f * 10.0f;
}

五、校准界面实现

5.1 校准参数数据结构

C#上位机中,每通道的校准参数word_7word_6

csharp 复制代码
public static class adcCalibParams {
    public static Int32[] calib_adcCalib_digitalA = new Int32[8];  // A点数字量
    public static Int32[] calib_adcCalib_analogA = new Int32[8];   // A点模拟量(×100000)
    public static byte[]   calib_adcCalib_enableA = new byte[8];   // A点使能
    public static Int32[] calib_adcCalib_digitalB = new Int32[8];  // B点数字量
    public static Int32[] calib_adcCalib_analogB = new Int32[8];   // B点模拟量
    public static byte[]   calib_adcCalib_enableB = new byte[8];   // B点使能
    public static Int32[] correctFactor = new Int32[8];             // 校正系数(×100000)
    public static Int32[] calibZero = new Int32[8];                 // 零点偏移(×100000)
    public static bool calibMode;                                    // 校准模式标志
    public static byte[] sensorUnit = new byte[8];                  // 传感器单位
}

5.2 两点校准算法

C#上位机中的两点线性校准公式word_3

csharp 复制代码
// msgStruct.cs
public static double calc_adcCalibData(double data, double d1, double a1, 
                                       double d2, double a2) {
    return (((data - d1) * (a2 - a1) / (d2 - d1)) + a1);
}

5.3 24位ADC数据转换

Setting.cs中的24位数据转换word_3

csharp 复制代码
// Setting.cs
if (_prxData.adcRange == 1)  // ±5V量程
    value = (SWAPS32(adcValue) - 8388608) / 83886.08 / 10;
else                          // ±10V量程
    value = (SWAPS32(adcValue) - 8388608) / 167772.16 / 10;
// 8388608 = 2^23, 24位ADC中位值

六、固件端的数据上传配合

6.1 采集端数据打包

固件中,波形数据满后的打包上传流程word_5

c 复制代码
if(_adcFunctionParam.adc_collectFullFlag == 1) {
    // 填充帧头
    _adcBuf.msgHead = SWAP16(0x55aa);
    _adcBuf.cmdId = _cmdId_getAdc_waveData;
    _adcBuf.frameLen = SWAP16(sizeof(_adcBuf));
    
    // 填充参数
    _adcBuf.adc_ch_Enable = _framDatas._adcParam.adc_ch_Enable;
    _adcBuf.trigCh = _framDatas._adcParam.trig_Ch;
    _adcBuf.samplingRate = SWAP16(_framDatas._adcParam.samplingRate);
    _adcBuf.trigValue = SWAP16(_framDatas._adcParam.trigValue);
    _adcBuf.adcRange = _framDatas._adcParam.adcRange;
    _adcBuf.adcSignalType = _framDatas._adcParam.adcSignalType;
    
    // CRC校验
    _adcBuf.crc = SWAP16(getCrc16WithTail((u8*)&_adcBuf, SWAP16(_adcBuf.frameLen)));
    
    // USB发送
    usb_sendMsg((u8*)&_adcBuf, SWAP16(_adcBuf.frameLen));
    
    _adcFunctionParam.adc_collectFullFlag = 0;
    _adcFunctionParam.deviceWorkMode = _deviceWorkMode_Stop;
}

6.2 数据采集流程

固件中的波形数据采集流程word_5

c 复制代码
if(_uadc.trig_flag == 1) {  // 已触发
    for(u8 i = 0; i < 8; i++) {
        if(_framDatas._adcParam.adc_ch_Enable & (1 << i)) {
            // 通道使能,存储数据
            if(_uadc.adc_collectIndex < (ADC_WAVE_ONCE_LEN - 1)) {
                // SWAP16大小端转换
                _adcBuf.data[_uadc.adc_collectIndex++] = SWAP16(_uadc.getAdc[i]);
            } else {
                // 缓冲区满,设置上传标志
                _adcFunctionParam.adc_collectFullFlag = 1;
                _adcFunctionParam.deviceWorkMode = _deviceWorkMode_sendBusy;
                break;
            }
        }
    }
}

七、传感器单位支持

7.1 预置单位列表

上位机预置17种传感器单位word_3

csharp 复制代码
public static string[] sensorUnitList = {
    "V", "mV",                    // 电压
    "A", "mA",                    // 电流
    "℃", "m℃",                    // 温度
    "N", "kg", "g",               // 力/重量
    "G", "K", "mK",               // 转速
    "MPa", "kPa", "hPa", "Pa",   // 压力
    "℉"                            // 华氏温度
};

7.2 自定义单位扩展

注意:D223支持最大256个自定义单位,可以任意修改note_259。用户可以根据实际传感器定义任意工程单位,远超17种预置单位。


八、实际测量数据示例

D223在实际使用中的测量精度示例word_5

8.1 输入1.000V时的8通道读数

通道 读数(V) 偏差(mV)
ADC_1 1.00276 +2.76
ADC_2 1.00251 +2.51
ADC_3 1.00234 +2.34
ADC_4 1.00182 +1.82
ADC_5 1.00206 +2.06
ADC_6 1.00176 +1.76
ADC_7 0.99975 -0.25
ADC_8 1.00011 +0.11

8.2 输入2.000V时的8通道读数

通道 读数(V) 偏差(mV)
ADC_1 1.99693 -3.07
ADC_2 1.99687 -3.13
ADC_3 1.99688 -3.12
ADC_4 1.99616 -3.84
ADC_5 1.99648 -3.52
ADC_6 1.99611 -3.89
ADC_7 1.99681 -3.19
ADC_8 1.99715 -2.85

数据显示未标定状态下偏差在±4mV以内,通过两点标定可将精度提升到0.01% FSRword_5note_244


九、二次开发建议

9.1 添加自定义命令

  1. 在msgStruct.cs中定义新的命令ID
  2. 在rxTim的命令分发switch中添加case
  3. 实现业务逻辑

9.2 修改波形显示

  1. 在WaveForm.cs中修改DrawWaveform函数
  2. 可以添加FFT分析、峰值检测、频率测量等功能

9.3 借助AI工具

ZLinear官方建议:可以借助AI工具轻松实现二次开发,自由定制上位机功能note_259。完整源代码 + AI辅助 = 快速定制。


十、总结

D223上位机C#开发的核心要点:

模块 关键文件 核心功能
帧解析 rxTim定时器 100ms检查、帧头查找、CRC校验
命令分发 msgStruct.cs 30+命令分类管理
波形显示 WaveForm.cs 数据接收、时间轴、波形绘制
校准管理 Setting.cs 两点校准、校正系数、24位转换
单位转换 msgStruct.cs 17种预置 + 256种自定义
大小端 SWAP16/SWAP32 协议大端 ↔ MCU小端

下篇预告:《D223真实应用场景案例:振动监测、电机测试与电力分析》

相关推荐
ACP广源盛139246256731 小时前
蚂蚁百灵 Ling‑3.0‑flash 开源 + 昇腾 0‑Day 原生适配@ACP#GSV9001E 在国产算力矩阵中的机会与落地场景
大数据·人工智能·分布式·单片机·嵌入式硬件
Poo_Chai1 小时前
QT emit信号后完整处理流程,包括槽函数响应流程
java·开发语言·数据库
瑞码空间1 小时前
Java 图形界面(GUI)完整知识点手册
java·开发语言·图形界面·swing
小努蛋2 小时前
linux编译openresty异常问题,lua_cjson.c:706:5: 错误: ‘for‘ 循环初始化声明只在 C99 或 C++ 模式下允许
开发语言·lua·openresty
qz_Serene2 小时前
C++:类和对象(上)
开发语言·c++
唐青枫2 小时前
别把 ImmutableArray 当成只读 List:C#.NET 不可变数组深入详解
c#·.net
西安景驰电子2 小时前
《PTP精确时间协议系列》第一篇:从原理到应用,全面解读IEEE 1588
linux·运维·服务器·开发语言·网络·windows·php
ctlover3 小时前
Python文件操作
开发语言·python