Labwindows CVI写上位机与STM32下位机通信(二)
来源:互联网 发布:淘宝怎么下载数据包 编辑:程序博客网 时间:2024/05/22 18:57
接着上篇,这篇主要谈谈流量计数据读取。我们使用的MEMS流量计采用Modbus协议Modbus 使用RS-485 作为硬件载体。
一. Modbus协议
该协议定义了 Modbus 总线Master(主设备)与Slave(从设备)之间的通讯报文格式,对于主设备来说,Modbus 协议是联系上位机(如PC、PLC、HMI 等)的接口,而且所有的通讯都是“透明的”。控制器通信使用主-从技术,即仅有一个设备(主设备)能初始化传输(查询),其它设备(从设备)根据主设备查询提供的数据作出相应反应。
主设备可单独和从设备通信,也能以广播方式和所有从设备通信(仅限于点对点通讯)。如果单独通信,从设备返回一帧消息作为回应;如果是以广播方式查询的,则不作任何回
应。Modbus 协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据和错误检测域。
从设备回应消息帧也由Modbus 协议构成,包括确认要产生动作的域、任何要返回的数据和错误检测域。如果在消息接收过程中发生错误,或从设备不能执行其命令,从设备将
建立一错误消息帧并把它作为回应发送出去。
控制器能设置为两种传输模式(ASCII 或RTU)中的任何一种在标准的Modbus 网络进行通信,用户选择想要的模式,包括串口通信参数(波特率、奇偶校验方式等),在配置每
个控制器的时候,在一个Modbus 网络上的所有设备都必须选择相同的传输模式和串口参数。所选的ASCII 或RTU 方式仅适用于标准的Modbus 网络,它定义了在这些网络上连续
传输的消息段的每一位,以及决定怎样将信息打包成消息域和如何解码。
1.1 modbus RTU模式
图1 RTU模式
使用RTU 模式,消息发送至少要以3.5 个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1-T2-T3-T4 所
示,相当于四个字节的传输延迟)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0~9,A~F。网络设备不断侦测网络总线,包括停顿间隔时间在内。当接收到第一个域(地址域)以后,每个设备都进行解码以判断是否是发往自己的。在最后一个传输字符之后,一个至少3.5 个字符时间的停顿确定了消息的结束。一个新的消息可在此停顿后开始。
图2 RTU典型消息帧
1.2 modbus编码
按照modbus RTU消息帧我们知道要读取modbus从设备数据,必须要按照协议走;首先发送设备地址,接着发送功能码,然后是数据位,最后是这帧指令CRC校验码。参照产品用户手册,以读取总流量为例:
图3 总流量寄存器
假设当前从设备地址为01,则发送:01 03 00 04 00 03 CRC_H CRC_L给流量计就可读回流量计数据,但读回的数据仍然是modbus RTU格式的,例如:
01 03 06 02 2B 00 00 00 64 CRC_H CRC_L,其中有效数据就是02 2B 00 00 00 64。关于其他功能编码,参照手册就可以了。这里附录一下CRC_16校验码产生的程序
#include "stdafx.h"typedef unsigned short UINT16;typedef unsigned char UINT8; typedef union{UINT16 val;struct{UINT16 bit0 : 1;UINT16 bit1 : 1;UINT16 bit2 : 1;UINT16 bit3 : 1;UINT16 bit4 : 1;UINT16 bit5 : 1;UINT16 bit6 : 1;UINT16 bit7 : 1;UINT16 bit8 : 1;UINT16 bit9 : 1;UINT16 bit10: 1;UINT16 bit11: 1;UINT16 bit12: 1;UINT16 bit13: 1;UINT16 bit14: 1;UINT16 bit15: 1;} bits;} TCRCRegs;TCRCRegs regs;// pBuf:要参与计算的消息缓冲区// nDataLen: CRC 要处理的字节的数量(消息缓冲区长度)UINT16 CRC1(UINT8 *pBuf, UINT16 nDataLen){ int i; UINT8 j,nTest; regs.val = 0xFFFF; for (i = 0; i < nDataLen; i++) { regs.val ^= *pBuf++; for (j = 0; j < 8; j++) { nTest = (regs.bits.bit0) ? 0x01:0x00; regs.val >>= 1; if (nTest == 1) regs.val ^= 0xA001; } } return regs.val;}/*******************CRC1返回的regs.val就是检验码*********************/int main(int argc, char* argv[]){ //unsigned char pBuf[6]={0x11,0x03,0x00,0x6B,0x00,0x03}; //unsigned char pBuf[6]={0x11,0x06,0x00,0x00,0x01,0x03}; unsigned char pBuf[13]={0x01,0x10,0x00,0x04,0x00,0x03,0x06,0x00,0x00,0x00,0x00,0x00,0x00}; CRC1(pBuf,6); return 0;}
1.3 流量计串口通信编程
考虑到要实时读取流量计数据,因此我们使用了一个定时器来控制读取数据的间隔,定时器定时时间间隔到就会执行定时器回调函数。在定时器回调函数中,发送读取流量计总流量和实时流量的命令。遇到的问题如下:
(1) 如果采用回调函数的形式读取接收的数据,会遇到这样一个问题:因为瞬时流量数据格式是9Byte,总流量数据格式是11Byte,怎样才能正确的解码数据?(不会串)
方法1:定义全局变量intrNum,每接收一个字符intrNum加1,然后做判断,代码如下:
void CVICALLBACK Traffic1Callback(int portNo,int eventMask, void *callbackData){ char readBuf[256] = {0}; intrNum++; if(intrNum == 9 && curProcessed == 0) { ComRd(traffic1Port.portNum,readBuf,9); .... intrNum = 0; curProcessed = 1; } else if(intrNum == 11) {<pre name="code" class="cpp"> ComRd(traffic1Port.portNum,readBuf,11); .... intrNum = 0; curProcessed = 0;
//流量计1对应的定时器int CVICALLBACK TrafficTimer1(int panel, int control, int event, void *callbackData, int eventData1, int eventData2){ switch(event) { case EVENT_TIMER_TICK: //定时间隔100ms FlushInQ(traffic1Port.portNum); //清空接收缓存区 FlushOutQ(traffic1Port.portNum); //清空发送缓存区 ComWrt(traffic1Port.portNum,totTraffic,sizeof(totTraffic)); //发送查询流量计1总流量命令 Delay(5); //延时5ms,保证RTU数据帧之间间隔 <pre name="code" class="cpp"> ComWrt(traffic1Port.portNum,totTraffic,sizeof(totTraffic)); //发送查询流量计1总流量命令
当不确定接收数据量的大小但知道数据帧大小时用计算这种方法来解决是很有效的,但是在Labwindows CVI的API中,没有每接收一个字符就执行一次回调函数的设定条件:
假如这样调用:
这样是不行的,从机流量计回发数据是一帧帧连续发送的,假设此时回发的是总流量数据,只有当11Byte数据发送完才会触发回调函数的执行,不会出现每接收一个数据执行一次回调,只要接受缓存中数据个数>=notifyCount就会触发回调函数。同理:
同时还有一个问题:定时器回调函数中的延时,当定时器回调函数还在执行时,串口回调函数要执行,这会造成线程冲突;只有等定时器回调函数执行完,串口回调函数才能执行。因此,这种解决方法不行。
(2) 轮训的方式,不再使用回调函数
开始时定时器时间间隔是50ms,程序运行时由于在定时器回调函数轮询,使得在面板上进行其他操作时程序会反应不过来,对应回调函数不会执行,同时程序会变得很卡甚至在任务管理器中是无响应状态。改为200ms后会改善很多,而且数据是绝对准确的,但是轮询在原理上不能解决其他事件不能及时处理的问题。代码如下:
<pre name="code" class="cpp">//流量计1对应的定时器int CVICALLBACK TrafficTimer1(int panel, int control, int event, void *callbackData, int eventData1, int eventData2){ double totalTraffic = 0.0; double traffic = 0.0; //设置流量 double trafficSpeed = 0.0; unsigned int regVal_02 = 0; unsigned int regVal_03 = 0; unsigned int regVal_04 = 0; unsigned int regVal_05 = 0; unsigned int regVal_06 = 0; int strLen = 0; unsigned char readBuf[256] = {0}; switch(event) { case EVENT_TIMER_TICK: //定时间隔100ms FlushInQ(traffic1Port.portNum); //清空接收缓存区 FlushOutQ(traffic1Port.portNum); //清空发送缓存区 ComWrt(traffic1Port.portNum,totTraffic,sizeof(totTraffic)); //发送查询流量计1总流量命令 while((strLen = GetInQLen(traffic1Port.portNum)) < 11); ComRd(traffic1Port.portNum,readBuf,11); regVal_04 = readBuf[3]*256 + readBuf[4]; regVal_05 = readBuf[5]*256 + readBuf[6]; regVal_06 = readBuf[7]*256 + readBuf[8]; totalTraffic = Compoundat((regVal_04*65536+regVal_05),regVal_06); //单位是mL //printf("总流量:%lf mL\n",totalTraffic); //打印结果 PlotStripChartPoint(panelHandle,PANEL_FLOW1METRECHART_1,totalTraffic); //显示数据 GetCtrlVal(panelHandle,PANEL_TRAFFIC_1,&traffic); if(totalTraffic>=traffic) { SetCtrlAttribute(panelHandle,PANEL_TRAFFIC1_TIMER,ATTR_ENABLED,0); //关闭定时器1 SendByte(uartPort,'J'); //关闭电磁阀1 SetCtrlVal(panelHandle,PANEL_VALVE1_CTRL,0); //将前面板上控制开关拨到OFF状态 MessagePopup("Message","总流量已达到设定值!"); } FlushInQ(traffic1Port.portNum); //清空接收缓存区 FlushOutQ(traffic1Port.portNum); //清空发送缓存区 ComWrt(traffic1Port.portNum,curTraffic,sizeof(curTraffic)); //发送查询流量计1流速查询命令 while((strLen = GetInQLen(traffic1Port.portNum)) < 9); ComRd(traffic1Port.portNum,readBuf,9); regVal_02 = readBuf[3]*256 + readBuf[4]; regVal_03 = readBuf[5]*256 + readBuf[6]; trafficSpeed = (double)(regVal_02*65536 + regVal_03)/1000; // PlotStripChartPoint(panelHandle,PANEL_TRAFFICSPEEDCHART_1,trafficSpeed); //显示流速数据 break; } return 0;}
(3) 仍然采用回调函数接收数据
定时器回调函数代码如下:
<pre name="code" class="cpp">//流量计1对应的定时器int CVICALLBACK TrafficTimer1(int panel, int control, int event, void *callbackData, int eventData1, int eventData2){ switch(event) { case EVENT_TIMER_TICK: //定时间隔100ms FlushInQ(traffic1Port.portNum); //清空接收缓存区 FlushOutQ(traffic1Port.portNum); //清空发送缓存区 ComWrt(traffic1Port.portNum,totTraffic,sizeof(totTraffic)); //发送查询流量计1总流量命令 Delay(0.010); //延时10ms,保证RTU数据帧之间间隔; 5ms测过了,不行 <pre name="code" class="cpp"> ComWrt(traffic1Port.portNum,totTraffic,sizeof(totTraffic)); //发送查询流量计1总流量命令
void CVICALLBACK Traffic1ComCallback(int portNo, int eventMask, void *callbackData){double totalTraffic = 0.0;double traffic = 0.0;double trafficSpeed = 0.0;unsigned int regVal_02 = 0;unsigned int regVal_03 = 0;unsigned int regVal_04 = 0;unsigned int regVal_05 = 0;unsigned int regVal_06 = 0;unsigned char readBuf[256] = {0};int strLen = 0;strLen = GetInQLen(traffic1Port.portNum); // 11 ComRd(traffic1Port.portNum,readBuf,strLen);regVal_04 = readBuf[3]*256 + readBuf[4];regVal_05 = readBuf[5]*256 + readBuf[6];regVal_06 = readBuf[7]*256 + readBuf[8];totalTraffic = Compoundat((regVal_04*65536+regVal_05),regVal_06); //单位是mL//printf("总流量:%lf mL\n",totalTraffic); //打印结果 PlotStripChartPoint(panelHandle,PANEL_FLOW1METRECHART_1,totalTraffic); //显示数据 GetCtrlVal(panelHandle,PANEL_TRAFFIC_1,&traffic);if(totalTraffic>=traffic){SetCtrlAttribute(panelHandle,PANEL_TRAFFIC1_TIMER,ATTR_ENABLED,0); //关闭定时器1 SendByte(uartPort,'J'); //关闭电磁阀1SetCtrlVal(panelHandle,PANEL_VALVE1_CTRL,0); //将前面板上控制开关拨到OFF状态MessagePopup("Message","总流量已达到设定值!");}regVal_02 = readBuf[14]*256 + readBuf[15];regVal_03 = readBuf[16]*256 + readBuf[17];trafficSpeed = (double)(regVal_02*65536 + regVal_03)/1000; //PlotStripChartPoint(panelHandle,PANEL_TRAFFICSPEEDCHART_1,trafficSpeed); //显示流速数据 }可以实现功能。但要注意,延时的时间间隔不能太短,设为5ms就不行。
- Labwindows CVI写上位机与STM32下位机通信(二)
- Labwindows CVI写上位机与STM32下位机通信(一)
- 智能车上位机与下位机通信的例子
- 【自己动手写上位机】串口通信
- LabWindows/CVI 下载与安装方法详解
- java实现上位机与下位机串口通信
- PC机与下位单片机异步串行通信
- PC机与下位单片机异步串行通信[摘]
- PC机与下位单片机异步串行通信
- labview与下位机通信的格式问题处理
- Labview通过RS422通讯接口PC与下位机通信
- Labview通过RS422通讯接口PC与下位机通信
- labwindows/CVI excel报表
- LabWindows/CVI 下载
- LabWindows/CVI线程操作
- Labwindows CVI 2013 一
- LabWindows/CVI对话框
- Visual C++、LabVIEW、LabWindows/CVI与MATLAB接口技术[zhuan]
- 算法总结(11)--伪递归,dfs,动态规划题,需要转换下思路
- HDU2091 打印 等腰空心字符串
- 一球从100米高空自由落下,每次落地后又反弹至原高度的一半,又落下,求它在第十次落地时,共经历多少米?第十次反弹多高?
- Linux的磁盘管理
- sphinx增量索引和主索引来实现索引的实时更新
- Labwindows CVI写上位机与STM32下位机通信(二)
- HDU2092 整数解 想起暴力电脑一愣一愣就想笑
- 图层叠加
- HDU2095 Map Stl 或异 找出奇数个数的数字 map stl 是啥!!!!
- 深入理解java 类在jvm中的生命周期
- 听清听力
- 猴子吃桃子问题:猴子第一天摘了若干个桃子,当时吃了若干个,还不瘾,又多吃了一个,第二天早上,又将剩下的桃子吃掉了一半,又多吃了一个。以后每一天早上都吃前一天剩下的一半零一个,直到第十天发现剩一个了,求
- iOS-多线程
- HDU2097 sky 数 就是进制转化 的问题