控制 plc的c#上位机或视觉上位机软件开发教程用c#哪部分语法多些吗?有项目资料吗?只搞软件部分不搞plc

错误CS0542VersionInfo:成员名不能与它们的封闭类型相同ClassLibrary1D:\***\Com.Baidu.Mapapi.VersionInfo.cs14活动的错误CS0102类...
Couldn'tfindavalidICUpackageinstalledonthesystem.SettheconfigurationflagSystem.Globalization.Invaria...
不等不说这是个大坑;官方文档:https://www.microsoft.com/net/learn/get-started/linux/ubuntu16-04没有什么卵用;你执行命令后,你会等到一个...
rejectedUpdateswererejectedbecausethetipofyourcurrentbranchisbehinditsremotecounterpart.Integratethe...
发布后提示:Error:assemblyspecifiedinthedependenciesmanifestwasnotfoundMicrosoft.ApplicationInsights.AspNe...
这里发布一个dotnetcore程序流程为说明;首先:发布代码如下图:进入:PublishOutput目录建立文件Dockerfile:内容如下FROMmicrosoft/dotnet:latestA...
OPC.Client for DA and UA 使用C...
西门子PLC数据块(DB)表达式
OPC 经典架构
西门子 s7通信 协议解读查看: 8361|回复: 85
请教,什么编程软件可快速完成编写一个上位机?
本人有c基础,没面对对象基础。
上位机只是从串口搜集单片机信息。
快速指的是入门比较简单,因为不是专门做it的,只要完成一些基础功能就好了。
c#吧 跨平台的话Qt不过相对c#要麻烦一些
我觉得delphi与c比较接近
VB------ C#
自古真相出二楼
作为电工,我觉得VB简单,串口下个例程改改就行了。不过VB很久的技术了
LabVIEW 也很好
可以试下VC#,比较好入门。
如果用在winxp等老平台 建议delphi 单文件,方便
c#上手很快的
有C基础就用C#,其它语言,你上手没那么快的。
不是有个全图形编程的软件吗,忘了叫什么了
Delphi,虽然很老了,但是很经典。
用VB吧,简单,入门容易,虽然技术老,但够用就行。
VB 最简单,有现在的代码,简单改一下,就成了你的项目
大家的回服很好。
VB上手快;C#也可以,就是做出来的软件需要.net运行环境,比较大。
论坛怎么没有手机App...
c#&&,主要和C很接近& &,这个上手也快。
我以亲身经历告诉你,&&选C#不错,重要的是你要搞清楚那几个类以及用法, 用C#最大的困难在于你不晓得用啥类或该类下面哪个函数来操作,这个要多看看别人的代码;
PS : 花了3天时间学习,每天只花1-2个小时看看视频,再看会对应的代码,做做笔记;&&然后1天半时间做出了人生第一个C#软件;
推荐C# , 可以快速上手 。 如何想搞专业点, 可以用QT
delphi吧,不用面向对象,c#像java所有的东西都是对象。delphi编写上位机挺好的,不愧是快速开发工具
E语言,一小时学会(据说的)
传说中的python
其实吧,用脚本语言就可以了,像python很多时候更方便
最好学习C#,这个以后用的久一点,开发速度也快,如果有c、vc基础,稍微看下语法,直接就可以上手了
C++ builder
labVIEW C++bulider,虽说是C++基本和C差不很多
建议用C#,不要用VB了,毕竟有点古老,虽然说能捉老鼠的就是好猫,但是还是C#有前景一些.
其他Delphi,QT之类的,没用过,不评论.
VC++的话,上手比C#难,相比而言.
Qt吧,快速高效,跨平台,都Qt5了,还支持Android.
LabVIEW 吧
delphi 7,版本老但好用,上手快
c#吧,上手简单,但是提高困难些。
感觉还是Qt方便一点 UI设计非常的简单&&剩下的就是自己去实现逻辑层代码&&
python& &入门简单, 也可以使用QT&&有IDE
建议QT。& && &
C#和QT我都自学过,上手都比较容易,个人感觉还是喜欢QT一点,再linux下也能用
本帖最后由
11:27 编辑
用微软的才是正道,当然DELPHI也可以。
上面有人说用QT,用脚本语言,看起来简单,不熟悉时根本不适合初学者。
QT在不用qtcreator时候,响应个按键消息都得自己添加信号槽什么的,对初学者来说天书一般。
而微软的C#之类软件只需要在控件上双击鼠标,函数体就出来了。
还有脚本语言有IDE么,有UI设计工具么?即使有也需要经过复杂的配置吧,靠敲代码实现UI很酷很简洁明了,但是适合初学者么?
C#两下就在窗体中把控件拉进来布置好了。
这就像武侠片里面的武器,普通人肯定是用刀剑简单直接高效,但是武功高手会说随便折根树枝就行,不用刀剑那么麻烦。
但是初学者的内功好像没达到用树枝的境界,所以一开始用刀剑才是最佳武器。
我觉得delphi与c比较接近
应该是c++builder吧.
用微软的才是正道,当然DELPHI也可以。
上面有人说用QT,用脚本语言,看起来简单,不熟悉时根本不适合初学 ...
顶起。我学过PYTHON加PYQT.这东西闹明白不是一天两天的事。。。想快速学习并加入开发最好还是--以前学过啥语言就用相近的语言做开发。
快速的话找个旧的在上面改改,是否可行,VC,VB应该都有
LabVIEW& && && && && && &
Labview& && &
用微软的才是正道,当然DELPHI也可以。
上面有人说用QT,用脚本语言,看起来简单,不熟悉时根本不适合初学 ...
用微软的才是正道,当然DELPHI也可以。
上面有人说用QT,用脚本语言,看起来简单,不熟悉时根本不适合初学者。
QT在不用qtcreator时候,响应个按键消息都得自己添加信号槽什么的,对初学者来说天书一般。
而微软的C#之类软件只需要在控件上双击鼠标,函数体就出来了。
还有脚本语言有IDE么,有UI设计工具么?即使有也需要经过复杂的配置吧,靠敲代码实现UI很酷很简洁明了,但是适合初学者么?
C#两下就在窗体中把控件拉进来布置好了。
这就像武侠片里面的武器,普通人肯定是用刀剑简单直接高效,但是武功高手会说随便折根树枝就行,不用刀剑那么麻烦。
但是初学者的内功好像没达到用树枝的境界,所以一开始用刀剑才是最佳武器。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
这位兄弟说的非常有道理,请楼主参考
快速开发,VB和Delphi传统的快速开发工具,现在C#也加入了他们的行列
c#,拖控件很快就搞出个上位机。
labview编写上位机方便
Python脚本,挺方便的
Labview& && &
要么VB,要么LabVIEW ,不过vb.net没有VB6.0上手快,但VB6.0在win7后续版本支持的非常不好!
Labview 或者 Matlab 都很好用的
作为一个软狗,我表示这贴我看的非常开心~
c#在这里的优势其实并非来自语言本身,而是微软提供的全太阳系最好的开发环境,从文档到工具,再到丰富的第三方资源。
c#的入门简直堪称现代编程语言中最easy的,而且完备的符合工程标准的工具可以保证软件开发的稳定。
当然,仅仅入门肯定是不够的,微软系的软件开发一直给人一种拖控件的错觉,其实并不是这样,拖控件仅仅是一种实现方式,背后是强大的工具和高度封装的基础库。
如果这样就够用了,肯定是最好的。一旦需求变更,系统复杂性上升,你会发现c#的强大之处绝不仅仅是可以在IDE里面拖控件这么简单。。。。
LZ可以先去下个VS express玩玩看,免费的哦~
有C基础可以试试C#
还是c#吧,我觉得会容易上点
最快的是用组态软件画一个,半个小时搞定。当然有一点限制,下位机只能用标准协议
labview 最好用, 图形化编程,很直观的
labview 最好用, 无需编程基础,串口通信,新接触labview,最多两天搞定,并且能显示曲线等。
用C#比较方便,就是太臃肿了
学了好长时间vc啊,就写了点小程序,一点入门的感觉也没有
本帖最后由 xieyudi 于
12:53 编辑
VC++ 6.0 或者普通的gcc+mingw, 纯C语言加上Windows的SDK, 就好, 半小时一个, 正好适合楼主.
大学时随手写的Tetris, VC++6.0, 纯C语言加上Windows的SDK, 不到700行, 界面和核心逻辑分离, 连写带测试4个小时.
至于串口住手什么的就更简单了.
小型程序就不要那些乱七八糟的好.
跨平台的话就QT吧, 做个小东西也挺简单的.
本帖子中包含更多资源
才可以下载或查看,没有帐号?
delphi上手还是挺快
delphi可能对你更容易些
个人感觉C#值得学习一下,可以很快地写出各种界面控制软件。
C#如果有C的基础,再加上会一些技巧(比较把万能的google搜索用好),基础上可以解决大部门我们这类工程师遇到的问题。
看了这么多,我决定用c#了
我现在也面临着这样的困境,看看这个就有方向了
我也感觉c#好学,一个月就可以写代码了
VB上手快,我用VB
NI labwindows CVI, 搭界面,和labview一样快。编程语言是C语言。串口之类的库,随手就可以使用,还有大量的例子,代码拷过去就能用了。
选TCL吧,最适合电工了
感觉C#还是比C++好入手啊
推荐C#,开始时大部分可以按C语言的语法写,大概了解一下它的不同就可以开工了,关键是有微软那套东西,直接可视编写出你想要的。而且这个还可以继续学,不落后。
C#的优点是语言简单、IDE功能强大、例程、教程多
Qt的优点是嵌入式Linux的GUI编程一般也用Qt
快速还是LabVIEW比较快
不是有个全图形编程的软件吗,忘了叫什么了
应该是说的 LabVIEW
LabVIEW开发还是比较快的,但需要使用各种运行库的支持才行。
我也需要写上位机软件,你有好的资料,咱们可以分享一下,。
VC6,一直在用,不难。
回复很有用,学习了
下载个微软的 visual studio。 具体选择上,可以选择VB,c++,c#,都大同小异。都是面向对象语言,都是很直观的拖放控件搞定。
C#容易,不过只要是自己来编码,都比较麻烦,主要是要缓冲,否则会数据不完整。
LabVIEW,你会爱上他, 电工的首选。
看了之后,我也要学c#了
楼主,你认为多快才算快啊?呵呵
我觉得QT不错
阿莫电子论坛, 原"中国电子开发网"
, 原www.ourdev.cn, 原www.ouravr.com开源一个c#上位机——串口调试助手源码 - 资料馆 - 意法半导体STM32/STM8技术社区
后使用快捷导航没有帐号?
查看: 3688|回复: 66
开源一个c#上位机——串口调试助手源码
在线时间39 小时
该用户从未签到主题帖子精华
是杰杰之前做的
一个参赛小作品
其实在做这个恒温控制系统项目的时候,师弟就问我,什么是上位机。。。。。可能很多师弟师妹都没一个大概的概念。
现在,就来看下什么是上位机:
& &&&上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等)。下位机是直接控制设备获取设备状况的计算机,一般是PLC/单片机single chip microcomputer/slave computer/lower computer之类的。上位机发出的命令首先给下位机,下位机再根据此命令解释成相应时序信号直接控制相应设备。下位机不时读取设备状态数据(一般为模拟量),转换成数字信号反馈给上位机。简言之如此,实际情况千差万别,但万变不离其宗:上下位机都需要编程,都有专门的开发系统。
在概念上,控制者和提供服务者是上位机,被控制者和被服务者是下位机,也可以理解为主机和从机的关系,但上位机和下位机是可以转换的。
&&&&两机如何通讯,一般取决于下位机,TCP/IP一般是支持的,但是下位机一般具有更可靠的独有通讯协议。通常上位机和下位机通讯可以采用不同通讯协议,可以有RS232的串口通讯或者采用RS485串行通讯。采用封装好的程序开发工具就可以实现下位机和上位机的通讯,当然可以自己编写驱动类的接口协议控制上位机和下位机的通讯。
通常工控机,工作站,触摸屏作为上位机,通信控制PLC,单片机等作为下位机,从而控制相关设备元件和驱动装置。
既然差不多知道什么是上位机与下位机,那么,我们做到小喇叭的要求:就得写个上位机,我自己也是学了下C#,用来开发上位机还是可以的,开发环境用visual studio 2015,微软的软件真的是很简单,之前看到有人问为什么微软的软件是最多人用的,答:因为那是傻瓜式操作。。。。。我不得不认同。。。&&
回归正题:先看看我们的上位机有什么功能:
1)能够实现与下位机的正常通讯。这必须得有,不然算哪门子上位机啊。
2)能够控制我们的恒温系统,通过电脑控制恒温系统的温度。这种应用场景很正常,我在机房就能控制我某一个地方的温度,简单方便。
3)能够实时显示温度与波形。这种应用的场景也是非常常见,实时显示温度我们可以知道温度是否正常,而波形我们能快速看出恒温系统空间温度是否出现异常。
4)数据保存功能,自动将实时的温度保存下来,加入 日期与时间,方便以后排查问题的时候用于检测某段时间的温度是否正常,这也是很使用的功能。
& && &说了那么多,看看我们的上位机。由于我以前用的别人的上位机都是色调比较单一的,而我,又是那种比较活泼开朗的人,觉得单一的灰色调比较死沉,我不喜欢,当然那是别人做的上位机,即使再不喜欢,要用还是要用,没办法,
现在我自己做的就不一样了,得给他做个五颜六色出来。亮瞎眼。。。。。
1.png (63.16 KB, 下载次数: 6)
14:44 上传
2.jpg (57.25 KB, 下载次数: 5)
14:44 上传
3.jpg (57.57 KB, 下载次数: 5)
14:44 上传
当我们的温度达到目标温度的时候,下位机就会控制自己的温度,以保持温度恒定,同时在上位机显示看到温度为一条曲线,由于我设置的温度为整数,没有小数,但是我们通过下位机可以看到实时的温度,精确在0.4范围左右。
4.jpg (58.44 KB, 下载次数: 5)
14:44 上传
当我们把上位机与下位机通讯协议切断,可以看到上位机接受不到数据(在软件右下角粉红色的窗口,没有显示接收到的数据),上位机的波形就会回到默认值(0°),就是上图的样子
当我们需要后期的检测与排除故障的时候,我们可以从我们保存的温度来做简单的排查,上位机自动保存的温度数据,精确到某天某时某分某秒,简单方便。也是一个很实用的功能当没什么故障的时候,我们只需要隔一段时间吧数据清一下就OK了。
附上作品视频:
本次上位机介绍到此结束
源码文件会在附件中。本上位机仅为杰杰个人娱乐,不做商业用途。
欢迎关注“创客飞梦空间”
干货不间断
在未来的日子里
创客飞梦空间与你们同在,放飞我们的梦想
微信截图_39.png (13.67 KB, 下载次数: 10)
14:44 上传
(385.68 KB, 下载次数: 69, 售价: 1 ST金币)
14:45 上传
点击文件名下载附件
售价: 1 ST金币 &
下载积分: ST金币 -1
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
在线时间1506 小时
ST金币3823
该用户从未签到主题帖子精华
为何要评论 才能看到
源码在隐藏文件&
在线时间614 小时
ST金币3763
该用户从未签到主题帖子精华
不错的内容~~
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
在线时间489 小时
ST金币5010
蝴蝶豆1221
TA的每日心情开心 11:47签到天数: 1 天[LV.1]初来乍到主题帖子精华
评论看内容
在线时间130 小时
ST金币1695
该用户从未签到主题帖子精华
金牌会员, 积分 2340, 距离下一级还需 2660 积分
评论看内容
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
在线时间39 小时
该用户从未签到主题帖子精华
为何要评论 才能看到
评论是源码
在线时间4 小时
该用户从未签到主题帖子精华
初级会员, 积分 57, 距离下一级还需 143 积分
正在学C#上位机,谢谢分享
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
在线时间39 小时
该用户从未签到主题帖子精华
正在学C#上位机,谢谢分享
在线时间1506 小时
ST金币3823
该用户从未签到主题帖子精华
这套路玩的有点深&&显示评论了 才能看到源码的附件&&评论玩了 又发现 还得要金币才能下载&&
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
在线时间39 小时
该用户从未签到主题帖子精华
这套路玩的有点深&&显示评论了 才能看到源码的附件&&评论玩了 又发现 还得要金币才能下载& &...
哈哈哈哈,好像下载附件都是要金币的吧,评论了又加金币嘛。。。不亏呢
STM32粉丝勋章Ⅳ
狂欢节专属(智多星)
STM32粉丝勋章Ⅲ
狂欢节专属(分享宝宝)
STM32粉丝勋章Ⅱ
狂欢节专属(研讨会问答)
STM32粉丝勋章Ⅰ
狂欢节专属(微信上墙)
站长推荐 /2
Tel: 3-8064
备案号: 苏ICP备号-2
|||意法半导体STM32/STM8技术社区
Powered byC#写了一款上位机监控软件,基于MODBUS_RTU协议。 软件的基本结构:
采用定时器(Timer控件)为时间片。
串口采用serialPort1_DataReceived中断接收,并进行MODBUS格式判断。
把正确接收的数据取出,转换为有特定的结构体中。
数据通过时间片实时刷新。
MODBUS协议(这里不介绍了,网上有很多的权威资料)。
  串口接收问题
这里采用的是MODBUS_RTU协议,是没有回车等明显的结束符的哈。所以在C#也不可以用serialPort1.ReadLine来读取。我用的是serialPort1.BytesToRead先读缓冲区中的数据个数,再通过个数据读数据。这样在用串口软件测试的时候确实很有用,再随之问题又出现了。下位机传上来的数据长度高出8个,就会分断接收。即接收到的两次的长度,第一次是8个,然后再接收到后面的。 原因是因为软件没有接收完一整帧数据后就进行了中断。解决方法:在中断中加入线程阻塞方法,然后再读取串口中的数据。
  发送读数据和发送写数据的结构
写了多个MODBUS协议的上位机后,总结了些经验,并将这部分程序封装在一个类中。
使用时只需对其接口函数调用即可,有很强的移植性。在写软件时不用再在协议这部分花太多的时间。
基本的使用方法在注释中。程序总体感觉 可能过于臃肿,希望各位大神批评指点。
以下是源代码:
* MODBUS协议
* 此modbus上位机 协议类 具有较强的通用性
* 本协议类最主要的思想是 把所有向下位机发送的指令 先存放在缓冲区中(命名为管道)
* 再将管道中的指令逐个发送出去。
* 管道遵守FIFO的模式。管道中所存放指令的个数 在全局变量中定义。
* 管道内主要分为两部分:1,定时循环发送指令。2,一次性发送指令。
* 定时循环发送指令:周期性间隔时间发送指令,一般针对&输入寄存器&或&输入线圈&等实时更新的变量。
* 这两部分的长度由用户所添加指令个数决定(所以自由性强)。
* 指令的最大发送次数,及管道中最大存放指令的个数在常量定义中 可进行设定。
* 使用说明:
* 1,首先对所定义的寄存器或线圈进行分组定义,并定义首地址。
* 2,在MBDataTable数组中添加寄存器或线圈所对应的地址。 注意 寄存器:ob = new UInt16()。线圈:ob = new byte()。
* 3,对所定义的地址 用属性进行定义 以方便在类外进行访问及了解所对应地址的含义。
* 4,GetAddressValueLength函数中 对使用说明的"第一步"分组 的元素个数进行指定。
* 5,在主程序中调用MBConfig进行协议初始化(初始化内容参考函数)。
* 6,在串口中断函数中调用MBDataReceive()。
* 7,定时器调用MBRefresh()。(10ms以下)
指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次
指令发送间隔为50ms。
* 8,在主程序初始化中添加固定实时发送的指令操作 用MBAddRepeatCmd函数。
* 9,在主程序运行过程中 根据需要添加 单个的指令操作(非固定重复发送的指令)用MBAddCmd函数。
* 作者:王宏强
38 using S
39 using System.Collections.G
40 using System.ComponentM
41 using System.D
42 using System.D
43 using System.T
44 using System.Windows.F
45 using System.IO.P
47 namespace WindowsApplication1
public class Modbus
#region 所用结构体
/// &summary&
/// 地址对应表元素单元
/// &/summary&
public struct OPTable{
public volatile int
public volatile byte
public volatile object
/// &summary&
/// 当前的指令
/// &/summary&
public struct MBCmd
public volatile int
//指令首地址
public volatile int
public volatile int
//所操作的寄存器或线圈的个数
public volatile int
//返回码的状态, 0:无返回,1:正确返回
/// &summary&
/// 当前操作的指令管道
/// &/summary&
public struct MBSci
public volatile MBCmd[]
//指令结构体
public volatile int
//当前索引
public volatile int
//当前功能码执行的次数
public volatile int maxRepeatC
//最大发送次数
public volatile int rtC
//实时读取的指令各数(无限间隔时间读取)
#endregion
#region 常量定义
public const byte MB_READ_COILS = 0x01;
//读线圈寄存器
public const byte MB_READ_DISCRETE = 0x02;
//读离散输入寄存器
public const byte MB_READ_HOLD_REG = 0x03;
//读保持寄存器
public const byte MB_READ_INPUT_REG = 0x04;
//读输入寄存器
public const byte MB_WRITE_SINGLE_COIL = 0x05;
//写单个线圈
public const byte MB_WRITE_SINGLE_REG = 0x06;
//写单寄存器
public const byte MB_WRITE_MULTIPLE_COILS = 0x0f;
//写多线圈
public const byte MB_WRITE_MULTIPLE_REGS = 0x10;
//写多寄存器
private const int MB_MAX_LENGTH = 120;
//最大数据长度
private const int MB_SCI_MAX_COUNT = 15;
//指令管道最大存放的指令各数
private const int MB_MAX_REPEAT_COUNT = 3;
//指令最多发送次数
#endregion
#region 全局变量
private static volatile bool sciLock = false;
//调度器锁 true:加锁
false:解锁
private static volatile byte[] buff = new byte[MB_MAX_LENGTH];
//接收缓冲器
private static volatile int buffLen = 0;
private static volatile byte[] rBuff = null;
//正确接收缓冲器
private static volatile byte[] wBuff = null;
//正确发送缓冲器
public static MBSci gMBSci = new MBSci() { cmd = new MBCmd[MB_SCI_MAX_COUNT], index = 0, maxRepeatCount = MB_MAX_REPEAT_COUNT, rtCount = 0, count = 0 };
private static SerialPort comm = null;
private static int mbRefreshTime = 0;
#endregion
#region MODBUS 地址对应表
//modbus寄存器和线圈分组 首地址定义
public const int D_DIO = 0x0000;
public const int D_BASE = 0x0014;
public const int D_RANGE = 0x0018;
public const int D_PWM = 0x001A;
public const int D_PID = 0x001E;
/// &summary&
/// 变量所对应的地址 在此位置
/// &/summary&
public static volatile OPTable[] MBDataTable =
new OPTable(){addr = D_DIO,
type = MB_READ_INPUT_REG,
ob = new UInt16()},
new OPTable(){addr = D_DIO + 1,
type = MB_READ_INPUT_REG,
ob = new UInt16()},
new OPTable(){addr = D_DIO + 2,
type = MB_READ_INPUT_REG,
ob = new UInt16()},
new OPTable(){addr = D_DIO + 3,
type = MB_READ_INPUT_REG,
ob = new UInt16()},
new OPTable(){addr = D_DIO + 4,
type = MB_READ_INPUT_REG,
ob = new Int16()},
new OPTable(){addr = D_DIO + 5,
type = MB_READ_INPUT_REG,
ob = new Int16()},
new OPTable(){addr = D_BASE,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_BASE + 1,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_BASE + 2,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_BASE + 3,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_RANGE,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_RANGE + 1,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_PWM,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_PWM + 1,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_PWM + 2,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_PWM + 3,
type = MB_READ_HOLD_REG,
ob = new Int16()},
new OPTable(){addr = D_PID,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
new OPTable(){addr = D_PID + 1,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
new OPTable(){addr = D_PID + 2,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
new OPTable(){addr = D_PID + 3,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
new OPTable(){addr = D_PID + 4,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
new OPTable(){addr = D_PID + 5,
type = MB_READ_HOLD_REG,
ob = new UInt16()},
public static UInt16 gDioX { get { return Convert.ToUInt16(MBDataTable[0].ob); } set { MBDataTable[0].ob = } }
public static UInt16 gDioY { get { return Convert.ToUInt16(MBDataTable[1].ob); } set { MBDataTable[1].ob = } }
public static UInt16 gDioZ { get { return Convert.ToUInt16(MBDataTable[2].ob); } set { MBDataTable[2].ob = } }
public static UInt16 gDioD { get { return Convert.ToUInt16(MBDataTable[3].ob); } set { MBDataTable[3].ob = } }
public static Int16 gDioXx { get { return (Int16)Convert.ToInt32(MBDataTable[4].ob); } set { MBDataTable[4].ob = } }
public static Int16 gDioXy { get { return (Int16)Convert.ToInt32(MBDataTable[5].ob); } set { MBDataTable[5].ob = } }
public static Int16 gBaseF1 { get { return (Int16)Convert.ToInt32(MBDataTable[6].ob); } set { MBDataTable[6].ob = } }
public static Int16 gBaseF2 { get { return (Int16)Convert.ToInt32(MBDataTable[7].ob); } set { MBDataTable[7].ob = } }
public static Int16 gBaseF3 { get { return (Int16)Convert.ToInt32(MBDataTable[8].ob); } set { MBDataTable[8].ob = } }
public static Int16 gBaseF4 { get { return (Int16)Convert.ToInt32(MBDataTable[9].ob); } set { MBDataTable[9].ob = } }
public static Int16 gRangeMax { get { return (Int16)Convert.ToInt32(MBDataTable[10].ob); } set { MBDataTable[10].ob = } }
public static Int16 gRangeMin { get { return (Int16)Convert.ToInt32(MBDataTable[11].ob); } set { MBDataTable[11].ob = } }
public static Int16 gPwmF1 { get { return (Int16)Convert.ToInt32(MBDataTable[12].ob); } set { MBDataTable[12].ob = } }
public static Int16 gPwmF2 { get { return (Int16)Convert.ToInt32(MBDataTable[13].ob); } set { MBDataTable[13].ob = } }
public static Int16 gPwmF3 { get { return (Int16)Convert.ToInt32(MBDataTable[14].ob); } set { MBDataTable[14].ob = } }
public static Int16 gPwmF4 { get { return (Int16)Convert.ToInt32(MBDataTable[15].ob); } set { MBDataTable[15].ob = } }
public static float gP
int tmp = (Convert.ToInt32(MBDataTable[16].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[17].ob) & 0xFFFF) && 16);
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, 0);
byte[] val = BitConverter.GetBytes(value);
MBDataTable[16].ob = BitConverter.ToUInt16(val, 0);
MBDataTable[17].ob = BitConverter.ToUInt16(val, 2);
public static float gI
int tmp = (Convert.ToInt32(MBDataTable[18].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[19].ob) & 0xFFFF) && 16);
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, 0);
byte[] val = BitConverter.GetBytes(value);
MBDataTable[18].ob = BitConverter.ToUInt16(val, 0);
MBDataTable[19].ob = BitConverter.ToUInt16(val, 2);
public static float gD
int tmp = (Convert.ToInt32(MBDataTable[20].ob) & 0xFFFF) | ((Convert.ToInt32(MBDataTable[21].ob) & 0xFFFF) && 16);
byte[] arr = BitConverter.GetBytes(tmp);
return BitConverter.ToSingle(arr, 0);
byte[] val = BitConverter.GetBytes(value);
MBDataTable[20].ob = BitConverter.ToUInt16(val, 0);
MBDataTable[21].ob = BitConverter.ToUInt16(val, 2);
public static UInt16 gNode = 100;
public static UInt16 gBaud = 38400;
/// &summary&
/// 获取寄存器或线圈 分组后的成员各数
/// &/summary&
/// &param name="addr"&首地址&/param&
/// &returns&成员各数&/returns&
private static int GetAddressValueLength(int addr)
int res = 0;
switch (addr)
case D_DIO: res = 6; break;
case D_BASE: res = 4; break;
case D_RANGE: res = 2; break;
case D_PWM: res = 4; break;
case D_PID: res = 6; break;
default: break;
/// &summary&
/// 获取地址所对应的数据
/// &/summary&
/// &param name="addr"&地址&/param&
/// &param name="type"&类型&/param&
/// &returns&获取到的数据&/returns&
private static object GetAddressValue(int addr, byte type)
switch (type)
//功能码类型判断
case MB_READ_COILS:
case MB_READ_DISCRETE:
case MB_READ_HOLD_REG:
case MB_READ_INPUT_REG: break;
case MB_WRITE_SINGLE_COIL:
case MB_WRITE_MULTIPLE_COILS: type = MB_READ_DISCRETE; break;
case MB_WRITE_SINGLE_REG:
case MB_WRITE_MULTIPLE_REGS: type = MB_READ_HOLD_REG; break;
default: return null;
for (int i = 0; i & MBDataTable.L i++)
if (MBDataTable[i].addr == addr)
if (MBDataTable[i].type == type)
return MBDataTable[i].
return null;
/// &summary&
/// 设置地址所对应的数据
/// &/summary&
/// &param name="addr"&地址&/param&
/// &param name="type"&类型&/param&
/// &param name="data"&数据&/param&
/// &returns&是否成功&/returns&
private static object SetAddressValue(int addr, byte type, object data)
for (int i = 0; i & MBDataTable.L i++)
if (MBDataTable[i].addr == addr)
if (MBDataTable[i].type == type)
MBDataTable[i].ob =
return true;
return null;
/// &summary&
/// 获取一连串数据
/// &/summary&
/// &param name="addr"&首地址&/param&
/// &param name="type"&功能码&/param&
/// &param name="len"&长度&/param&
/// &returns&转换后的字节数组&/returns&
private static byte[] GetAddressValues(int addr, byte type, int len)
byte[] arr = null;
int temp2;
switch (type)
case MB_WRITE_MULTIPLE_COILS:
arr = new byte[(len % 8 == 0) ? (len / 8) : (len / 8 + 1)];
for (int i = 0; i & arr.L i++)
for (int j = 0; j & 8; j++)
//获取地址所对应的数据 并判断所读数据 是否被指定,有没被指定的数据 直接返回null
obj = GetAddressValue(addr + i * 8 + j, MB_READ_COILS);
if (obj == null)
return null;
temp = Convert.ToByte(obj);
(byte)((temp == 0? 0 : 1) && j);
case MB_WRITE_MULTIPLE_REGS:
arr = new byte[len * 2];
for (int i = 0; i & i++)
obj = GetAddressValue(addr + i, MB_READ_HOLD_REG);
if (obj == null)
return null;
temp2 = Convert.ToInt32(obj);
arr[i * 2] = (byte)(temp2 && 8);
arr[i * 2 + 1] = (byte)(temp2 & 0xFF);
default: break;
#endregion
#region 校验
private static readonly byte[] aucCRCHi = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
private static readonly byte[] aucCRCLo = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
/// &summary&
/// CRC效验
/// &/summary&
/// &param name="pucFrame"&效验数据&/param&
/// &param name="usLen"&数据长度&/param&
/// &returns&效验结果&/returns&
public static int Crc16(byte[] pucFrame, int usLen)
int i = 0;
byte ucCRCHi = 0xFF;
byte ucCRCLo = 0xFF;
UInt16 iIndex = 0x0000;
while (usLen-- & 0)
iIndex = (UInt16)(ucCRCLo ^ pucFrame[i++]);
ucCRCLo = (byte)(ucCRCHi ^ aucCRCHi[iIndex]);
ucCRCHi = aucCRCLo[iIndex];
return (ucCRCHi && 8 | ucCRCLo);
#endregion
#region 发送指命操作
/// &summary&
/// 首部分数据 node:节点
/// &/summary&
/// &param name="addr"&寄存器地址&/param&
/// &param name="len"&数据长度,或单个数据&/param&
/// &param name="stat"&&/param&
/// &returns&&/returns&
private static byte[] SendTrainHead(int node, int addr, int len, byte stat)
byte[] head = new byte[6];
head[0] = Convert.ToByte(node);
head[2] = (byte)(addr && 8);
head[3] = (byte)(addr & 0xFF);
head[4] = (byte)(len && 8);
head[5] = (byte)(len & 0xFF);
/// &summary&
/// 计算数据长度 并在0x0f,0x10功能下 加载字节数
/// &/summary&
/// &param name="arr"&&/param&
/// &param name="len"&&/param&
/// &param name="stat"&&/param&
/// &returns&&/returns&
private static byte[] SendTrainBytes(byte[] arr, ref int len, byte stat)
switch (stat)
default: len = 0; break;
case MB_READ_COILS:
case MB_READ_DISCRETE:
case MB_READ_HOLD_REG:
case MB_READ_INPUT_REG:
case MB_WRITE_SINGLE_COIL:
case MB_WRITE_SINGLE_REG:
case MB_WRITE_MULTIPLE_COILS:
len = (len % 8 == 0) ? (len / 8) : (len / 8 + 1);
res = new byte[arr.Length + 1];
arr.CopyTo(res, 0);
res[arr.Length] = (byte)(len);
case MB_WRITE_MULTIPLE_REGS:
res = new byte[arr.Length + 1];
arr.CopyTo(res, 0);
res[arr.Length] = (byte)
//把字节写入数据最后位置
/// &summary&
/// 主控方式
发送指令模板
/// &/summary&
/// &param name="node"&节点&/param&
/// &param name="data"&数据&/param&
/// &param name="addr"&地址&/param&
/// &param name="con"&变量各数&/param&
/// &param name="stat"&功能码&/param&
/// &returns&&/returns&
private static byte[] SendTrainCyclostyle(int node, byte[] data, int addr, int con, byte stat)
int crcVal = 0;
byte[] headData = SendTrainHead(node, addr, con, stat);
//写首部分数据
byte[] headDataLen = SendTrainBytes(headData, ref con, stat);
//计算数据的长度,有字节则写入。
byte[] res = new byte[headDataLen.Length + con + 2];
headDataLen.CopyTo(res, 0);
if ((stat == MB_WRITE_MULTIPLE_REGS) || (stat == MB_WRITE_MULTIPLE_COILS))
Array.Copy(data, 0, res, headDataLen.Length, con);
//把数据复制到数据中
crcVal = Crc16(res, res.Length - 2);
res[res.Length - 2] = (byte)(crcVal & 0xFF);
res[res.Length - 1] = (byte)(crcVal && 8);
/// &summary&
/// 封装发送数据帧
/// &/summary&
/// &param name="node"&从机地址&/param&
/// &param name="cmd"&指令信息&/param&
/// &returns&&/returns&
private static byte[] SendPduPack(int node, MBCmd cmd)
byte[] res = null;
switch (cmd.stat)
case MB_READ_COILS:
case MB_READ_DISCRETE:
case MB_READ_HOLD_REG:
case MB_READ_INPUT_REG:
case MB_WRITE_SINGLE_COIL:
case MB_WRITE_SINGLE_REG:
res = SendTrainCyclostyle(node, null, cmd.addr, cmd.len, (byte)cmd.stat); break;
case MB_WRITE_MULTIPLE_COILS:
case MB_WRITE_MULTIPLE_REGS:
byte[] data = GetAddressValues(cmd.addr, (byte)cmd.stat, cmd.len);
res = SendTrainCyclostyle(node, data, cmd.addr, cmd.len, (byte)cmd.stat); break;
#endregion
#region 回传数据操作
/// &summary&
/// 存储回传的线圈
/// &/summary&
/// &param name="data"&回传的数组&/param&
/// &param name="addr"&首地址&/param&
/// &returns&存储是否正确&/returns&
private static bool ReadDiscrete(byte[] data, int addr)
bool res = true;
int len = data[2];
if (len != (data.Length - 5))
//数据长度不正确 直接退出
return false;
for (int i = 0; i & i++)
for (int j = 0; j & 8; j++)
if (SetAddressValue(addr + i * 8 + j, data[1], data[i + 3] & (0x01 && j)) == null)
return false;
/// &summary&
/// 读回传的寄存器
/// &/summary&
/// &param name="data"&回传的数组&/param&
/// &param name="addr"&首地址&/param&
/// &returns&存储是否正确&/returns&
private static bool ReadReg(byte[] data, int addr)
bool res = true;
int len = data[2];
if (len != (data.Length - 5))
//数据长度不正确 直接退出
return false;
for (int i = 0; i & i += 2)
if (SetAddressValue(addr + i / 2, data[1], (data[i + 3] && 8) | data[i + 4]) == null)
res = false;
/// &summary&
/// 回传的数据处理
/// &/summary&
/// &param name="buff"&回传的整帧数据&/param&
/// &param name="addr"&当前所操作的首地址&/param&
/// &returns&&/returns&
private static bool ReceiveDataProcess(byte[] buff, int addr)
if (buff == null)
return false;
if (buff.Length & 5)
//回传的数据 地址+功能码+长度+2效验 = 5字节
return false;
bool res = true;
switch (buff[1])
case MB_READ_COILS: ReadDiscrete(buff, addr); break;
case MB_READ_DISCRETE: ReadDiscrete(buff, addr); break;
case MB_READ_HOLD_REG: ReadReg(buff, addr); break;
case MB_READ_INPUT_REG: ReadReg(buff, addr); break;
case MB_WRITE_SINGLE_COIL:
case MB_WRITE_SINGLE_REG:
case MB_WRITE_MULTIPLE_COILS:
case MB_WRITE_MULTIPLE_REGS: break;
default: res = false; break;
#endregion
#region 收发调度
/// &summary&
/// 添加重复操作指令
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
/// &param name="addr"&所添加指令的首地址&/param&
/// &param name="len"&所添加指令的寄存器或线圈个数&/param&
/// &param name="stat"&所添加指令的功能码&/param&
private static void SciAddRepeatCmd(ref MBSci sci, int addr, int len, int stat)
if (sci.rtCount &= MB_SCI_MAX_COUNT - 1)
//超出指令管道最大长度 直接退出
if (len == 0)
//地址的数据长度为空 直接退出
sci.cmd[sci.rtCount].addr =
sci.cmd[sci.rtCount].len =
sci.cmd[sci.rtCount].stat =
sci.cmd[sci.rtCount].res = 0;
sci.rtCount++;
/// &summary&
/// 添加一次性操作指令
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
/// &param name="addr"&所添加指令的首地址&/param&
/// &param name="len"&所添加指令的寄存器或线圈个数&/param&
/// &param name="stat"&所添加指令的功能码&/param&
private static void SciAddCmd(ref MBSci sci, int addr, int len, int stat)
if (len == 0)
//地址的数据长度为空 直接退出
for (int i = sci.rtC i & MB_SCI_MAX_COUNT; i++)
if (sci.cmd[i].addr == -1)
//把指令载入到空的管道指令上
sci.cmd[i].addr =
sci.cmd[i].len =
sci.cmd[i].stat =
sci.cmd[i].res = 0;
/// &summary&
/// 清空重复读取指令集
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
private static void SciClearRepeatCmd(ref MBSci sci)
sci.rtCount = 0;
/// &summary&
/// 清空一次性读取指令集
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
private static void SciClearCmd(ref MBSci sci)
for (int i = sci.rtC i & MB_SCI_MAX_COUNT; i++)
sci.cmd[i].addr = -1;
sci.cmd[i].len = 0;
sci.cmd[i].res = 0;
/// &summary&
/// 跳到下一个操作指令
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
private static void SciJumbNext(ref MBSci sci)
if (sci.index &= sci.rtCount)
//非实时读取地址会被清除
sci.cmd[sci.index].addr = -1;
sci.cmd[sci.index].len = 0;
sci.cmd[sci.index].stat = 0;
sci.index++;
if (sci.index &= MB_SCI_MAX_COUNT)
//超出指令最大范围
sci.index = 0;
if (sci.rtCount == 0)
//如果固定实时读取 为空 直接跳出
} while (sci.cmd[sci.index].addr == -1);
sci.cmd[sci.index].res = 0;
//本次返回状态清零
/// &summary&
/// 发送指令调度锁定
/// &/summary&
public static void SciSchedulingLock()
sciLock = true;
/// &summary&
/// 发送指令调度解锁
/// &/summary&
public static void SciSchedulingUnlock()
sciLock = false;
/// &summary&
/// 待发送的指令管道调度
/// &/summary&
/// &param name="sci"&待发送的指命管道&/param&
/// &param name="rBuf"&收到正确的回传数据&/param&
/// &param name="wBuf"&准备发送的指令数据&/param&
private static void SciScheduling(ref MBSci sci, ref byte[] rBuf, ref byte[] wBuf)
if (sciLock)
//如果被加锁 直接退出
if ((sci.cmd[sci.index].res != 0) || (sci.count &= sci.maxRepeatCount))
sci.count = 0;
//发送次数清零
if (sci.cmd[sci.index].res != 0)
//如果收到了正常返回
ReceiveDataProcess(rBuf, sci.cmd[sci.index].addr);
//保存数据
rBuf = null;
//清空当前接收缓冲区的内容, 以防下次重复读取
//参数操作失败
SciJumbNext(ref sci);
wBuf = SendPduPack((int)gNode, sci.cmd[sci.index]);
//发送指令操作
sci.count++;
//发送次数加1
/// &summary&
/// 快速刷新 处理接收到的数据
建议:10ms以下
/// &/summary&
/// &returns&所正确回传数据的功能码, null:回传不正确&/returns&
private static int MBQuickRefresh()
int res = -1;
if (rBuff != null)
SciSchedulingLock();
if (ReceiveDataProcess(rBuff, gMBSci.cmd[gMBSci.index].addr) == true)
gMBSci.cmd[gMBSci.index].res = 1;
//标记 所接收到的数据正确
res = gMBSci.cmd[gMBSci.index].
rBuff = null;
SciSchedulingUnlock();
/// &summary&
/// 调度间隔时间刷新
建议:50ms以上
/// &/summary&
/// &returns&封装好的协议帧&/returns&
private static void MBSchedRefresh()
SciScheduling(ref gMBSci, ref rBuff, ref wBuff);
if (wBuff != null)
comm.Write(wBuff, 0, wBuff.Length);
#endregion
#region 接口函数
/// &summary&
/// 清空存放一次性的指令空间
/// &/summary&
public static void MBClearCmd()
SciClearCmd(ref gMBSci);
/// &summary&
/// 添加固定刷新(重复) 操作指令
/// &/summary&
/// &param name="addr"&地址&/param&
/// &param name="stat"&功能码&/param&
public static void MBAddRepeatCmd(int addr, byte stat)
for (int i = 0; i & GetAddressValueLength(addr); i++ )
if (GetAddressValue(addr, stat) == null)
//如果所添加的指令没有在MODBUS对应表中定义 直接退出
SciAddRepeatCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
/// &summary&
/// 添加一次性 操作指令
/// &/summary&
/// &param name="addr"&&/param&
/// &param name="stat"&&/param&
public static void MBAddCmd(int addr, byte stat)
for (int i = 0; i & GetAddressValueLength(addr); i++)
if (GetAddressValue(addr, stat) == null)
//如果所添加的指令没有在MODBUS对应表中定义 直接退出
SciAddCmd(ref gMBSci, addr, GetAddressValueLength(addr), stat);
/// &summary&
/// 串口参数配置
/// &/summary&
/// &param name="commx"&所用到的串口&/param&
/// &param name="node"&&/param&
/// &param name="baud"&&/param&
public static void MBConfig(SerialPort commx, UInt16 node, UInt16 baud)
SciClearRepeatCmd(ref gMBSci);
SciClearCmd(ref gMBSci);
/// &summary&
/// 读取串口中接收到的数据
/// &/summary&
/// &param name="comm"&所用到的串口&/param&
public static void MBDataReceive()
if (comm == null)
//如果串口没有被初始化直接退出
SciSchedulingLock();
System.Threading.Thread.Sleep(20);
//等待缓冲器满
buffLen = comm.BytesToR
//获取缓冲区字节长度
if (buffLen & MB_MAX_LENGTH)
//如果长度超出范围 直接退出
SciSchedulingUnlock();
comm.Read(buff, 0, buffLen);
//读取数据
if (gMBSci.cmd[gMBSci.index].stat == buff[1])
if (Crc16(buff, buffLen) == 0)
rBuff = new byte[buffLen];
Array.Copy(buff, rBuff, buffLen);
SciSchedulingUnlock();
/// &summary&
/// MODBUS的实时刷新任务,在定时器在实时调用此函数
/// 指令发送间隔时间等于实时器乘以10。 例:定时器5ms调用一次
指令发送间隔为50ms。
/// &/summary&
/// &returns&返回当前功能读取指令回传 的功能码&/returns&
public static int MBRefresh()
if (sciLock)
//如果被加锁 直接退出
mbRefreshTime++;
if (mbRefreshTime & 10)
mbRefreshTime = 0;
MBSchedRefresh();
return MBQuickRefresh();
#endregion
下面是自己开发的一个小控制软件及原代码:
原文件上传到我的网盘中:
提示:这个小软件用了第三方插件Developer Express v2011。确认安装此插件方能正常打开。
下面这个小工具是用modbus发送 大块数据的样例:
日志 BUG修改:
1,如下图增加 ,修正在无重复指令时,单次指令的次数的正确性。
if&(sci.cmd[0].addr&==&-1)&&&&&&&&&&&&&&&&
阅读(...) 评论()

我要回帖

更多关于 工控上位机软件开发 的文章

 

随机推荐