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中,没有每接收一个字符就执行一次回调函数的设定条件:
假如这样调用:

InstallComCallback (Port_Number, LWRS_RECEIVE,1, 0,ComCallbackPtr Callback_Function, NULL);

这样是不行的,从机流量计回发数据是一帧帧连续发送的,假设此时回发的是总流量数据,只有当11Byte数据发送完才会触发回调函数的执行,不会出现每接收一个数据执行一次回调,只要接受缓存中数据个数>=notifyCount就会触发回调函数。同理:

InstallComCallback (Port_Number, LWRS_RXCHAR,0, 0,ComCallbackPtr Callback_Function, NULL); 也不行。

同时还有一个问题:定时器回调函数中的延时,当定时器回调函数还在执行时,串口回调函数要执行,这会造成线程冲突;只有等定时器回调函数执行完,串口回调函数才能执行。因此,这种解决方法不行。


(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) 仍然采用回调函数接收数据

InstallComCallback (Port_Number, LWRS_RECEIVE,20, 0,ComCallbackPtr Callback_Function, NULL);  //接收到一阵总流量和一帧瞬时流量数据执行一次回调函数

定时器回调函数代码如下:

<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就不行。









0 0
原创粉丝点击