当前位置: 首页 > news >正文

硬件与软件交互全解析:协议、控制与数据采集实践

1. 系统概述

1.2 硬件架构

┌─────────────────────────────────────────────────────┐
│                   医疗检测设备                      │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐  │
│  │  泵    │  │ 电磁阀  │  │  传感器 │  │  LED   │  │
│  │(抽气)  │  │(开关)  │  │(检测)  │  │(指示)  │  │
│  └────────┘  └────────┘  └────────┘  └────────┘  │
│           ↓                                      │
│       ┌──────────┐                               │
│       │  主控板   │                               │
│       │ (Arduino) │                               │
│       └─────┬─────┘                               │
│             │ RS232                               │
│             ↓                                    │
└─────────────┼─────────────────────────────────────┘↓┌──────────┐│   电脑   ││  (Java)  │└──────────┘

1.3 通信方式

  • 接口:RS232 串口(COM1/COM2/COM3...)
  • 速率:9600/115200bps
  • 协议:自定义二进制

2. 通信协议详解

2.1 协议帧结构

┌─────┬─────┬────────┬────────┬────────┬─────┐
│ 0x5A│ 0xA5│ 长度+5  │设备ID高│设备ID低│命令 │
└─────┴─────┴────────┴────────┴────────┴─────┘固定头    固定头+─────┬─────┬─────┬──────┐
│参数1│参数2│参数3│校验和│
└─────┴─────┴─────┴──────┘
示例解析(控制泵速度 = 6)
// 十六进制表示
5A A5 08 02 05 B8 06 00 XX// 字节分解
0x5A        → 固定头1
0xA5        → 固定头2  
0x08        → 数据长度(8字节) + 5
0x02        → 主控板ID高字节
0x05        → 应用模块ID(5=泵控制模块)
0xB8        → 命令字(控制泵)
0x06        → 泵速度低字节(6)
0x00        → 泵速度高字节(0)
0xXX        → 校验和

2.2 设备 ID 定义

ID模块功能
0x02主控板协调各模块
0x01APP1电磁阀控制
0x03APP3电压采集
0x05APP5泵控制
0x06APP6打印控制

2.3 创建协议帧

协议帧 = 固定格式的 “数据容器”,专门解决 “双方怎么准确传数据” 的问题。

// NewProtocol.java 第123-164行
public byte[] createOrder(final Order order) {byte nCount = (byte) (order.commad_str[1] & 0x0f);byte[] orders = new byte[nCount + 8];// 固定头orders[0] = 0x5a;orders[1] = (byte) 0xa5;orders[2] = (byte) ((1 << 7) + nCount + 5);  // 长度+标志// 设备IDorders[3] = (byte) (order.commid_str[0] & 0x1f);orders[4] = (byte) (order.commid_str[1] & 0x1f);// 命令orders[5] = order.commad_str[0];orders[6] = (byte) ((order.commad_str[1] & 0xf0) >> 4);// 参数(1-8个)switch (nCount) {case 8: orders[14] = order.part4[1];case 7: orders[13] = order.part4[0];case 6: orders[12] = order.part3[1];case 5: orders[11] = order.part3[0];case 4: orders[10] = order.part2[1];case 3: orders[9] = order.part2[0];case 2: orders[8] = order.part1[1];case 1: orders[7] = order.part1[0];}// 计算校验和int num = 0;for (int i = 3; i < (nCount + 7); i++) {num += orders[i];}orders[nCount + 7] = (byte) num;return orders;
}

3. 硬件控制接口详解

3.1 泵控制 ctrlPump(int nRate)

// 第248-266行
public void ctrlPump(int nRate) {Order order = new Order();order.commid_str[0] = MASTER_ID;      // 0x02 主控板order.commid_str[1] = APP5_ID;        // 0x05 泵模块order.commad_str[0] = COMMAND_HGKGP_PUMP;  // 0xB8 命令// 速度值(16位)order.part1[0] = (byte) (nRate & 0xff);     // 低字节order.part1[1] = (byte) ((nRate >> 8) & 0xff); // 高字节base.sendOrder(new String(createOrder(order), "XXXXXX"));
}
  • 速度参数含义:0= 停止,6= 低速采集,8= 中速清空,10= 快速清空

3.2 电磁阀控制 ctrlValves(int nIndex, boolean bOpen)

// 第269-299行
public void ctrlValves(int nSample, boolean bOpen) {Order order = new Order();order.commid_str[0] = MASTER_ID;       // 0x02 主控板order.commid_str[1] = APP1_ID;         // 0x01 电磁阀模块order.commad_str[0] = COMMAND_HGKGP_SETTAP;  // 0xXXif (bOpen) {order.commad_str[1] = (byte) ((COMMAND_SETTAP_OPEN << 4) + 3);} else {order.commad_str[1] = (byte) ((COMMAND_SETTAP_CLOSE << 4) + 3);}// 根据电磁阀编号选择地址if (nSample < 6) {order.part1[0] = ADDR_TAP1_APP1;      // 地址块1order.part2[0] = (byte) (nSample);     // 1-5号阀} else if (nSample >= 6 && nSample < 12) {order.part1[0] = ADDR_TAP2_APP1;      // 地址块2order.part2[0] = (byte) (nSample - 6); // 6-11号阀} else if (nSample >= 12) {order.part1[0] = ADDR_TAP3_APP1;      // 地址块3order.part2[0] = (byte) (nSample - 12); // 12+号阀}base.sendOrder(new String(createOrder(order), "XXXXXX"));
}

物理示例:开 1 号阀→抽 1 号瓶气体,开 2 号阀→抽 2 号瓶气体。

3.3 采样控制 sampleValue(int port)

// 第381-391行
public void sampleValue(int nSample) {if (nSample <= 10) {ctrlValves(1 + nSample, true);  // 打开对应电磁阀} else if ((nSample > 10) && (nSample < 16)) {ctrlValves(2 + nSample, true);} else if (nSample == 16) {ctrlValves(1, true);}
}// 示例:sampleValue(1)
// → ctrlValves(2, true)
// → 打开2号电磁阀
// → 从1号采样瓶抽气

调用示例:sampleValue(1) 打开 2 号电磁阀,抽取 1 号采样瓶气体;sampleValue(2) 打开 3 号电磁阀,抽取 2 号采样瓶气体。

3.4 零点控制 zeroValue(boolean bOpen)

// 第374-378行
public void zeroValue(boolean bOpen) {logger.info("send order 5...");ctrlValves(12, bOpen);  // 控制12号电磁阀
}
  • 零点阀作用:切换环境空气,建立测量基准;关闭时接通样气。

3.5 循环模式 circlMode(boolean bOpen)

// 第343-371行
public void circlMode(boolean bOpen) {Order order = new Order();order.commid_str[0] = MASTER_ID;order.commid_str[1] = APP1_ID;order.commad_str[0] = COMMAND_HGKGP_SETTAP;if (bOpen) {order.commad_str[1] = (byte) ((COMMAND_SETTAP_OPEN << 4) + 4);} else {order.commad_str[1] = (byte) ((COMMAND_SETTAP_CLOSE << 4) + 4);}// 控制0号电磁阀ctrlValves(0, bOpen);
}
  • 开循环:气体在腔体循环复用,稳定测量;闭循环:气体单向流动,完成采集后清空。

3.6 LED 控制 ctrlLeds(int nIndex, boolean bOpen)

// 第302-334行
public void ctrlLeds(int nIndex, boolean bOpen) {Order order = new Order();order.commid_str[0] = MASTER_ID;order.commid_str[1] = APP1_ID;order.commad_str[0] = COMMAND_HGKGP_SETLED;order.commad_str[1] = (byte) ((COMMAND_SETLED_FLASH << 4) + 3);// 根据索引选择LED地址int nPort;if (nIndex <= 10) {nPort = 4 - (nIndex - 1) / 2;order.part1[0] = ADDR_LED1_APP1;  // LED地址块1} else {nPort = (nIndex - 1) / 2 - 4;order.part1[0] = ADDR_LED2_APP1;  // LED地址块2}if (bOpen) {order.part1[1] = (byte) ((0x01 << nPort));  // 点亮} else {order.part1[1] = (byte) (0x00);  // 熄灭}base.sendOrder(new String(createOrder(order), "xxxxxxx"));
}

用途:指示当前端口与设备工作状态。

4. 数据采集:硬件→软件

// MainFrame.java 第3109-3158行
case NewProtocol.COMMAND_HGKGP_REALV1V:// 解包数据int nData = (order.commad_str[1] >> 4) & 0x0f;  // 采样次数// C12电压(32位)int nV1 = ((order.part2[1]& 0xff)<<24) | ((order.part2[0]&0xff)<<16) | ((order.part1[1]&0xff)<<8) | (order.part1[0]&0xff);// C13电压(32位)int nV2 = ((order.part4[1]& 0xff)<<24) | ((order.part4[0]&0xff)<<16) | ((order.part3[1]&0xff)<<8) | (order.part3[0]&0xff);// 计算平均电压double C12Sum = nV1 * 1.0 / nData;double C13Sum = nV2 * 1.0 / nData;// 添加到实时数据数组RealTimeData.addRealTimeData(C12Sum, C13Sum);// 界面更新SwingUtilities.invokeLater(new Runnable() {public void run() {votageChart.addVotageSeriesData(C12Sum, C13Sum);lC12Votage.setText(String.format("12C:%.2f V", C12Sum));lC13Votage.setText(String.format("13C:%.2f V", C13Sum));// 计算并显示厚度gasChart.addThickSeriesData(ThicknessData.caculateC12ThickChange(C12Sum),ThicknessData.caculateC13ThickChange(C13Sum));}});

5. 串口通信实现

5.1 初始化串口

private boolean openCom() {// 1. 查找串口portId = CommPortIdentifier.getPortIdentifier("COM1");// 2. 打开串口(超时2秒)serialPort = (SerialPort) portId.open("Serial Communication", 2000);// 3. 设置参数serialPort.setSerialPortParams(9600,        // 波特率SerialPort.DATABITS_8,   // 数据位SerialPort.STOPBITS_1,   // 停止位SerialPort.PARITY_NONE   // 校验位);// 4. 获取输入输出流outputStream = serialPort.getOutputStream();  // 发送inputStream = serialPort.getInputStream();   // 接收// 5. 添加监听器(接收数据)serialPort.addEventListener(new serialPortListener());serialPort.notifyOnDataAvailable(true);bIsConnected = true;return true;
}

5.2 发送数据

// SerialComm.java 第252-287行
public boolean sendOrder(String strOrder) {if (strOrder != null) {try {// 检查串口状态if (b_com_status && outputStream != null && bIsConnected) {// 转换为字节并发送outputStream.write(strOrder.getBytes("xxxxxxxx"));return true;}} catch (Exception e) {logger.error(e);// 出错时关闭连接close();return false;}}return false;
}

5.3 接收数据

// SerialComm.java 第65-124行
public class serialPortListener implements SerialPortEventListener {public void serialEvent(SerialPortEvent event) {switch (event.getEventType()) {case SerialPortEvent.DATA_AVAILABLE:  // 有数据到达while (newData != -1) {newData = inputStream.read();  // 读一个字节// 解析协议帧if (receiveData(newData)) {// 完整帧,通知监听者for (CommunicationListener listener : listeners) {listener.dataAvailable(new String(m_RecBuf, 0, m_RecNum, "xxxxx"));}// 清空缓冲区m_RecNum = 0;}}break;case SerialPortEvent.OE:  // 溢出错误logger.info("溢位错误");break;// ... 其他错误}}
}

5.4 协议帧解析

// SerialComm.java 第311-396行
private boolean receiveData(int nData) {byte uReChar = (byte) nData;// 步骤1:检测帧头 0x5Aif ((0x5A == uReChar) && (0 == m_RecNum)) {m_RecBuf[0] = uReChar;m_RecNum = 1;return false;}// 步骤2:检测第二字节 0xA5if ((0x5A == m_RecBuf[0]) && (0xA5 == uReChar)) {m_RecBuf[1] = uReChar;m_RecNum = 2;return false;}// 步骤3:读取数据长度if (m_RecNum == 2) {m_RecBuf[2] = uReChar;int nRec = uReChar & 0x7f;if (nRec < 4 || nRec > 21) {// 长度异常,清空m_RecNum = 0;return false;}M_RECNUMB = nRec;  // 保存数据长度m_RecNum = 3;return false;}// 步骤4:读取数据部分if (m_RecNum < M_RECNUMB + 2) {m_RecBuf[m_RecNum] = uReChar;m_RecNum++;return false;}// 步骤5:读取校验和并验证if (m_RecNum == M_RECNUMB + 2) {m_RecBuf[m_RecNum] = uReChar;  // 校验和// 计算校验和int nResult1 = 0;for (int i = 3; i < M_RECNUMB - 1; i++) {nResult1 += m_RecBuf[i];}nResult1 = nResult1 & 0xff;// 验证if (nResult1 == nData) {return true;  // 校验通过} else {return false;  // 校验失败}}return false;
}

6. 数据计算:从电压到 DOB 值

7.1 电压→光强

// RealTimeData.java 第187-189行
private static double caculateVotage(double dLight) {return dLight * 4.096 / 65535;  // 16位ADC转换
}

7.2 光强→浓度

// ThicknessData.java
public static double caculateC12ThickChange(double light) {// 光强 → C12浓度return ...; // 复杂算法
}public static double caculateC13ThickChange(double light) {// 光强 → C13浓度return ...; // 复杂算法
}

7.3 Delta 计算

// ThicknessData.java 第111-113行
public static double caculateDelta(double c12, double c13) {// Delta = ((C13/C12)/10000 - 标准值) * 1000 / 标准值return ((c13/c12)/10000 - 0.01123686) * 1000 / 0.01123686;
}

Delta 含义:相对标准偏差(千分率)

7.4 DOB 最终计算

// MainFrame.java 第3515-3528行
// Delta30分钟
double del30 = -25 + (delta_3 - delNihe_3);// Delta0分钟(基准)
double del00 = -25 + (delta_0 - delNihe_0);// DOB = Delta30 - Delta0
double temp = del30 - curDel00;// DLL修正
temp = DllUtil.INSTANCE.caculate(curThick, c12, temp);// 保存最终结果
status.setDob(temp);// 判断结果
if (temp >= 4.0) {阳性(+)  // 检测到幽门螺杆菌
} else {阴性(-)  // 未检测到
}

7. 调试与常见问题

7.1 串口连接问题

// 现象:无法打开串口
// 可能原因:COM口被占用、波特率不匹配、驱动问题// 解决方案
try {portId = CommPortIdentifier.getPortIdentifier("COM1");serialPort = (SerialPort) portId.open("Serial Communication", 2000);
} catch (NoSuchPortException e) {MessageBoxUtil.showMessageBox(null, "串口不存在");
} catch (PortInUseException e) {MessageBoxUtil.showMessageBox(null, "串口被占用");
}

7.2 数据接收异常

// 现象:接收不到数据
// 检查点:
// 1. 串口是否打开
if (!bIsConnected) {logger.error("串口未连接");return;
}// 2. 数据校验是否正确
if (checksum != calculatedChecksum) {logger.info("数据校验失败");return;
}// 3. 监听器是否注册
serialPort.notifyOnDataAvailable(true);

7.3 硬件响应超时

// 现象:发送指令后硬件无响应
// 解决方案:增加重试机制public boolean sendOrderWithRetry(String order, int maxRetries) {for (int i = 0; i < maxRetries; i++) {if (sendOrder(order)) {// 等待响应Thread.sleep(100);if (checkResponse()) {return true;}}}return false;
}
http://www.dtcms.com/a/541810.html

相关文章:

  • 国内外网站建设2017php网站怎么做的
  • 离石古楼角网站建设合肥有哪些做网站的公司
  • 二叉树的锯齿形层序遍历
  • Java8:新日期时间
  • Java_String对象特性
  • 网站做app的软件有哪些360安全浏览器
  • 网站建设 互成网络amp 网站开发
  • 网站app免费生成软件下载免费 片
  • USB基础知识--Endpoint与pipe
  • SpringBoot拦截器实战与原理剖析
  • 把握智能语音风口:云蝠智能【声・纪元】VoiceAgent 实时语音智能论坛邀您同行
  • 一文吃透二叉树、完全平衡树、红黑树原理及C语言实现
  • 做网站用别人的图片沈阳设计公司排名
  • 浙江自己如何做网站wordpress 做后台
  • 网站 模板下载陕西富通建设有限公司网站
  • 淄博高效网站建设免费网站建站模板
  • Bootstrap4 Jumbotron详解与使用指南
  • IoT技术在产线实践中的应用
  • 合格VR大空间企业:核心要素有哪些?
  • 06.OpenStack网络管理
  • C++学习记录(23)智能指针
  • 网站内容策划方案wordpress底部版权信息修改
  • python 在class中几种函数的定义和用法
  • 电商数据中台基石:通过 API 构建淘宝商品实时数据源
  • 川崎机器人焊接电源气体省气
  • 理想汽车基于 Hologres + Flink 构建万亿级车联网信号实时分析平台
  • php教育视频网站开发如何做古诗词网站
  • 自发购卡网站在吗做手机建立网站软件
  • Git Tag 理解和使用
  • 如何写一个WebRTC ACE音频应用处理模块