拿到D223的开源上位机源码后如何理解和修改?本文从C#代码层面,解析上位机的帧接收解析、CRC校验、波形绘制和校准界面实现。
一、上位机架构总览
D223的C#上位机基于WinForms开发,使用Visual Studio 2022社区版[word_3][word_10]。核心代码结构[word_3][word_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_6][note_63]:
// 帧结构[帧头:0x55AA][命令ID:1B][帧长度:2B][数据段:NB][CRC16:2B]C#结构体定义[word_3]:
publicstructmsgHead{publicushortmsgHead;// 0x55AA(大端)publicbytecmdId;// 命令IDpublicushortframeLen;// 帧总长度(大端)}3.3 大小端处理
STM32是小端,通信协议用大端[word_3]:
// 大小端转换宏#defineSWAP16(x)((((x)&0xFF)<<8)|(((x)>>8)&0xFF))#defineSWAP32(x)((((x)&0xFF)<<24)|(((x)&0xFF00)<<8)|\(((x)>>8)&0xFF00)|(((x)>>24)&0xFF))帧头在内存中存储为0xAA55,但发送到USB线上是0x55 0xAA[note_63]。
3.4 CRC16校验
上位机使用CRC16-Modbus算法[word_6][note_63]:
publicstaticushortgetCrc16WithTail(byte[]data,intlen){bytecrcL=0xFF,crcH=0xFF;len-=2;// 减去CRC本身2字节while(len-->0){intindex=crcH^data[dataOffset++];crcH=(byte)(crcL^tableH[index]);crcL=tableL[index];}return(ushort)((crcH<<8)|crcL);}CRC16-Modbus参数:
- 初始值:0xFFFF
- 多项式:0xA001
- 查表法加速,预计算256个值的tableH和tableL[pdf_127]
四、波形数据接收与显示
4.1 波形数据包结构
采集卡上传的波形数据包结构[word_5]:
structadcWaveBuf{ushortmsgHead;// 0x55AAbytecmdId;// 波形数据命令IDushortframeLen;// 帧长度byteadcStartCh;// 起始通道号byteadc_ch_Enable;// 通道使能位掩码bytetrigCh;// 触发通道ushorttrigValue;// 触发值ushortsamplingRate;// 采样率byteadcRange;// 量程byteadcSignalType;// 信号类型short[]data;// ADC波形数据ushortcrc;// CRC16}4.2 数据接收解析
上位机接收波形数据的处理流程[word_5]:
voidOnWaveDataReceived(byte[]data){// 解析数据包ushortmsgHead=(data[0]<<8)|data[1];// 0x55AAbytecmdId=data[2];ushortframeLen=(data[3]<<8)|data[4];// 提取波形参数byteadcStartCh=data[5];byteadcChEnable=data[6];bytetrigCh=data[7];ushorttrigValue=(data[8]<<8)|data[9];ushortsamplingRate=(data[10]<<8)|data[11];// 提取ADC数据intdataOffset=12;intdataLen=frameLen-dataOffset-2;// 减去CRCshort[]waveData=newshort[dataLen/2];for(inti=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]:
voidDrawWaveform(short[]data,intsamplingRate,inttrigValue){// 计算时间轴floattimeStep=1.0f/samplingRate;// 每点的时间间隔// 绘制网格DrawGrid();// 绘制波形for(inti=0;i<data.Length-1;i++){floatx1=i*timeStep;floaty1=AdcToVoltage(data[i]);// ADC值转电压floatx2=(i+1)*timeStep;floaty2=AdcToVoltage(data[i+1]);DrawLine(x1,y1,x2,y2);}// 标记触发点MarkTriggerPoint(trigValue);// 显示测量参数DisplayMeasurements(data);}4.4 ADC值转电压
floatAdcToVoltage(shortadcValue){// 根据量程转换if(adcRange==1)// ±5Vreturn(adcValue-32768)/32768.0f*5.0f;else// ±10Vreturn(adcValue-32768)/32768.0f*10.0f;}五、校准界面实现
5.1 校准参数数据结构
C#上位机中,每通道的校准参数[word_7][word_6]:
publicstaticclassadcCalibParams{publicstaticInt32[]calib_adcCalib_digitalA=newInt32[8];// A点数字量publicstaticInt32[]calib_adcCalib_analogA=newInt32[8];// A点模拟量(×100000)publicstaticbyte[]calib_adcCalib_enableA=newbyte[8];// A点使能publicstaticInt32[]calib_adcCalib_digitalB=newInt32[8];// B点数字量publicstaticInt32[]calib_adcCalib_analogB=newInt32[8];// B点模拟量publicstaticbyte[]calib_adcCalib_enableB=newbyte[8];// B点使能publicstaticInt32[]correctFactor=newInt32[8];// 校正系数(×100000)publicstaticInt32[]calibZero=newInt32[8];// 零点偏移(×100000)publicstaticboolcalibMode;// 校准模式标志publicstaticbyte[]sensorUnit=newbyte[8];// 传感器单位}5.2 两点校准算法
C#上位机中的两点线性校准公式[word_3]:
// msgStruct.cspublicstaticdoublecalc_adcCalibData(doubledata,doubled1,doublea1,doubled2,doublea2){return(((data-d1)*(a2-a1)/(d2-d1))+a1);}5.3 24位ADC数据转换
Setting.cs中的24位数据转换[word_3]:
// Setting.csif(_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]:
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]:
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]:
publicstaticstring[]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% FSR[word_5][note_244]。
九、二次开发建议
9.1 添加自定义命令
- 在msgStruct.cs中定义新的命令ID
- 在rxTim的命令分发switch中添加case
- 实现业务逻辑
9.2 修改波形显示
- 在WaveForm.cs中修改DrawWaveform函数
- 可以添加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真实应用场景案例:振动监测、电机测试与电力分析》