简介:面向C#开发与工业自动化工程师,资源包提供基于MC协议访问基恩士、三菱PLC寄存器的完整实现方案。内容覆盖D、W、X、Y寄存器及INT16、INT32、FLOAT、DOUBLE等数据类型读写,适合需要实现上位机与PLC数据交互、远程监控的开发者。压缩包共70个文件,约176KB,以C#源码(21个cs)、项目配置(csproj、json、props)及说明文档(md、txt)为主,并包含可运行的解决方案与示例工程,便于直接研究或二次开发。目前已有1314人学习下载。通过阅读源码可掌握连接建立、寄存器寻址、数据解析与连接释放等关键环节,为快速集成三菱或基恩士PLC通信能力提供实用参考。 这两年做产线追溯项目,几乎每次都会被同一个问题卡住:基恩士的扫码枪已经到了,三菱PLC的程序也改好了,但两边怎么把数据接通,一直没人给一个准话。做视觉也好、扫码也好,基恩士是出镜率最高的那批设备,而三菱PLC在中小型产线里又是绝对的主力。标题里写的“支持基恩士、三菱PLC MC协议”,说白了就是在这样一个背景下被逼出来的——做一个上位机中间件,一头接基恩士扫码枪的TCP/IP数据,另一头通过MC协议把条码写进三菱PLC,顺带把触发、完成、异常这些信号也一并打通。
这套方案适合谁?适合正在做产线追溯、防错防呆、设备联网的项目,尤其是第一次碰基恩士扫码枪或者第一次碰三菱MC协议的工程师。下面把协议报文、设备对接、调试踩坑都按实际做过的路子整理出来,照着走能少折腾好几天。
1. 先搞清楚基恩士和三菱PLC在产线上各自扮演什么角色
1.1 一套标准的扫码追溯架构,其实有三层
产线上的“扫码枪读到条码,PLC决定放行还是拦截”听起来简单,实际上中间必须有个翻译。基恩士SR1000这类读码器,即使是支持TCP/IP的型号,它吐出来的也只是一串ASCII字符。PLC那边要的不是字符串,而是写入D寄存器或M继电器的具体数据,这中间必须有人完成一次从“字符流”到“MC协议报文”的转换。
我的做法是做一个常驻上位机网关,三层的分工是这样的:基恩士扫码枪负责采集条码,按设定好的通讯方式把结果发出来;上位机软件作为中间桥梁,接收条码后按规则解析,再调用MC协议指令写入三菱PLC;PLC侧则通过程序判定数据合法性,并给出OK或NG信号。这个架构的好处是,调试的时候每一层都可以单独验证:先用TCP工具测扫码枪的数据输出,再用MC协议调试器测PLC读写,最后把两边串起来。
1.2 MC协议为什么是“必须会”的通信方式
MC协议(Mitsubishi Communication Protocol)是三菱PLC以太网通信最常用的协议,往上走有MC协议、SLMP、内置以太网功能,本质上核心都是一套东西。不管你是用Q系列内置以太网口、QJ71E71以太网模块,还是L系列,只要走以太网和上位机通信,绝大部分情况都会落到MC协议上。
顺带说一句,昆仑通泰触摸屏连三菱Q系列,底层用的也是MC协议,只是触摸屏组态软件帮你把报文封装好了。所以很多人会遇到“触摸屏能连上,自己写程序却连不上”的怪现象——原因往往是触摸屏占用了通信通道,或者PLC侧的连接数已经满了。这个问题后面单独说。
1.3 基恩士扫码枪的通讯方式怎么选
基恩士SR1000系列支持RS-232C、RS-422、以太网等多种输出方式。在以太网模式下,可以把它配置成TCP客户端主动连上位机,也可以配置成TCP服务端等上位机来连。实际项目里我更喜欢让扫码枪作为TCP服务端,上位机去做客户端连接,这样扫码枪重启后它自己会监听端口,上位机只负责掉线重连,逻辑上更稳定。
也有厂家直接把基恩士扫码枪接到PLC的串口或以太网上,通过MC协议让PLC直接读扫码枪数据。但这种做法对PLC编程要求高,而且调试权限全在设备侧,出了问题很难定位。我的建议是:如果现场已经有上位机(工控机、触摸屏一体机),就让上位机来做中转,灵活性和可维护性都更好。
2. 三菱侧的核心:把MC协议报文彻底拆开
2.1 3E帧的结构与字节序,容不得半点错
MC协议在以太网传输时主要用3E帧,它和串口上的1E帧/4E帧有明显区别。3E帧里藏着一个固定头,然后是网络号、PC号、IO编号这些“路由信息”,后面才是真正的请求数据。最容易摔跟头的地方是字节序:MC协议里,字数据都是低字节在前,也就是小端存储。比如D100这个地址,十进制100转成十六进制是0x0064,在报文里要写成64 00,而不是00 64。
一个典型的批量读取D寄存器请求帧是这样的(十六进制):
50 00 00 FF FF 03 00 0B 00 10 00 01 04 00 A8 64 00 00 02 00其中:
50 00:子帧头,固定值,标识这是3E帧以太网访问00:网络号,通常填0FF:PC号,通常填0xFFFF 03:请求目标模块IO编号,默认0x03FF,低字节在前00:请求目标模块站号0B 00:请求数据长度,这里是从“监视定时器”到“点数”的11个字节10 00:CPU监视定时器,单位是250ms,0x0010表示4秒超时01 04:指令,代表“批量读取(字单位)”00 A8:设备类型,0x00A8表示D数据寄存器64 00 00:起始地址,即D10002 00:要读取的点数,这里读2个字
如果PLC正常返回,响应帧会是:
50 00 00 FF FF 03 00 09 00 00 00 64 00 C8 00第9、10字节是结束代码,00 00表示正常。后面每2个字节就是一个字的数据。
2.2 读和写,两个指令包覆盖90%的需求
批量写入的指令是01 14,也是按字写入。比如要把D100和D101分别写成200和0,请求帧是:
50 00 00 FF FF 03 00 0F 00 10 00 01 14 00 A8 64 00 00 02 00 C8 00 00 00对比读取就能发现,只是指令从01 04换成了01 14,数据段尾部追加了要写入的值,数据长度也相应多了4个字节。对,你要提前自己算好这个长度,PLC不会帮你纠正,长度写错,帧直接就被丢弃了。
批量读取位设备时指令换成01 01,设备码也跟着换:X输入是9C 00,Y输出是94 00,M中间继电器是90 00。位设备的响应并不是一个BIT对应一个字节,而是把若干个BIT按位打包返回,这和你做MODBUS读取线圈数据时的处理方式是一样的。
所以我在项目里原则是:需要反馈到PLC时,优先用D寄存器传数据、M寄存器传状态。因为M寄存器写起来能直接驱动PLC内部逻辑,在触摸屏上也能直接看到状态,调试期非常友好。
2.3 设备码表与地址换算,写错一个就全盘皆输
这里放一张我常用的设备码对照表,省得每次翻手册:
| 设备 | 设备类型码(字单位) | 设备类型码(位单位) | 说明 |
|---|---|---|---|
| X输入 | -- | 0x009C | 十六进制地址,如X0对应0x00 |
| Y输出 | -- | 0x0094 | 十六进制地址,如Y10对应0x10 |
| M继电器 | -- | 0x0090 | 十进制地址,如M0对应0x00 |
| L锁存继电器 | -- | 0x0092 | 十进制地址 |
| D数据寄存器 | 0x00A8 | -- | 十进制地址 |
| W链接寄存器 | 0x00B4 | -- | 十六进制地址 |
| R文件寄存器 | 0x00AF | -- | 十进制地址 |
| B链接继电器 | -- | 0x00A0 | 十六进制地址 |
地址换算最大的坑是:X、Y、W这类设备,地址序号在PLC里显示为十六进制,比如Y10,它实际的十六进制地址就是0x0010。而D、M、R这些设备序号是十进制,直接用就行。你要是把Y10当成十进制10去算,报文里的地址就会变成0x000A,那操作就跑到Y10前面的某个点上去了。
基恩士和三菱在协议栈里的处理策略很不同。基恩士的TCP/IP通讯更偏向“把数据发出去就算完”,你有疑问还得自己从返回值里抠。三菱MC协议则是一问一答,必须有响应,而且在数据链路层相对严谨。这两个风格放在一个项目里,需要你在上层做一点点适配,不至于冲突。
3. 基恩士扫码枪接入:数据从条码到变量的全过程
3.1 SR1000的网络参数设置和触发模式
基恩士SR1000的通信参数,通常不是在本机菜单里设的,而是用基恩士的专用设置软件(如SR1000的AutoID网络通讯配置工具)通过以太网连上后改配置。需要关注的几个点:
- IP地址、子网掩码、网关,必须和上位机在同一个网段
- 协议类型:选TCP/IP
- 传输模式:选“TCP服务端”还是“TCP客户端”,以及监听端口号
- 数据输出格式:是触发后输出一次,还是持续输出;是ASCII原始数据,还是带前缀/结束码的格式
如果现场没有专用软件,也可以先用底层网络扫描工具确认读码器的IP,然后用TCP/UDP调试工具去连它的端口。实测下来,SR1000监听的默认端口一般是9000口,也可以自定义,这在上位机配置里要能灵活填。
触发模式这块,我一般不开“连续读取”,而是用传感器触发或外部信号触发。否则扫码枪会高频吐数据,上位机队列很容易被同一张条码反复塞满。如果你在产线调试时发现PLC里的条码一直在变,先别查PLC,大概率是扫码枪一直在重复扫描。
3.2 上位机接收扫码结果的程序骨架
扫码枪把条码按ASCII字符串发出来,通常以回车换行结尾。上位机这边只需要一个简单的TCP监听流程。C#里最直接的写法是这样的:
TcpListener listener = new TcpListener(IPAddress.Any, 9000); listener.Start(); while (true) { // 等待扫码枪连接 using TcpClient client = await listener.AcceptTcpClientAsync(); using NetworkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; int read = await stream.ReadAsync(buffer); string recv = Encoding.ASCII.GetString(buffer, 0, read); string barcode = recv.TrimEnd('\r', '\n'); Console.WriteLine($"Scan: {barcode}"); }这只是骨架,实际上必须处理沾包和半包。TCP是流式协议,扫码枪一次发送的ASCII也可能因为网络断开被分成两次到达,或者一次把上次的残留一起带过来。处理办法是维护一个接收缓冲区,按结束符(CR/LF)切包,而不是每次都把收到的数据当成完整一帧。
3.3 条码数据的清洗和防重策略
基恩士扫码枪的输出格式可能带一些“附加物”,比如在数据前面加[S]、后面加[E],或者加一个表示读取成功与否的头部标记。在项目早期,我建议把收到的原始数据先打印出来,确认格式后再做截取,不要在没看清原始数据的情况下直接做字符串处理。
条码里的空格也要特别注意。很多二维码本身不含空格,但扫码枪的默认配置可能会在数据内插入分组符号。遇到这种情况,先确认码制,确认条码内容本身是什么,再决定是保留还是剔除。
防重逻辑建议做三层:上位机记录最后一次读取内容,如果和上次相同,并且两次间隔小于设定阈值,就直接丢弃;写入PLC后要等PLC返回“已处理”信号,在此之前不再接收新的扫码结果;队列中只保留最新一条条码,避免积压。
4. 网关核心:怎么把扫码数据安全写进三菱PLC
4.1 整体架构:一个监听线程,一个写PLC线程
我在工程里一般起两个线程:一个专门接收扫码数据,把合法的条码放进阻塞队列;另一个专门消费队列,逐条调用MC协议客户端写入PLC。这比“收到一条就立刻写PLC”的模式稳定得多,因为当PLC侧超时或上位机卡顿时,扫码枪不会阻塞,产线不会因为上位机在处理旧数据而漏掉新扫码。
代码层面,可以用Channel或者BlockingCollection来当队列。每次扫码结果是典型的“生产”事件,写PLC失败重试是“消费”过程,两者解耦后,排查问题也容易——看队列长度就知道是接收端卡了还是PLC写入卡了。
4.2 字符串条码转成D寄存器数据,要注意ASCII长度
三菱PLC的D寄存器一个占16位,也就是2个字节。一个条码字符串如果要存到D寄存器,先要把字符转成ASCII码,然后每两个字节拼成一个字。比如条码是“AB12”,对应的ASCII十六进制是41 42 31 32,那么D100里存0x4241,D101里存0x3231。
写入代码的核心部分如下:
public void WriteBarcodeToD(int startAddress, string barcode) { byte[] ascii = Encoding.ASCII.GetBytes(barcode); int wordCount = (ascii.Length + 1) / 2; ushort[] values = new ushort[wordCount]; for (int i = 0; i < ascii.Length; i += 2) { ushort v = ascii[i]; if (i + 1 < ascii.Length) v |= (ushort)(ascii[i + 1] << 8); values[i / 2] = v; } plc.WriteWords(0xA8, startAddress, values); plc.WriteBit(0x90, 100, true); // 置位M100,通知PLC有新的条码 }这样写有个隐含约定:PLC侧解析时,要按同样的“低字节在前”逻辑把D寄存器还原成字符串。所以上位机和PLC程序最好由同一个工程师统一规划,或者两头文档写得清清楚楚。最怕的是上位机按小端写入,PLC侧按大端解析,出来的字符串就会是乱的。
还有一点,条码里如果包含中文,就需要用UTF-8或Shift-JIS编码,而不是ASCll,否则会丢字符。扫码枪的条码一般以ASCII为主,但万一项目里扫的是带中文的QR码,这块要提前测。
4.3 写入完成后的确认和重试
MC协议是“发指令-等响应”的模式,所以写入是否成功,直接看响应帧的结束代码就行。正常情况下结束代码是00 00,不为0就是PLC侧拒绝了请求。我在网关里对写入失败的策略是:重试3次,每次间隔500ms;如果3次都不行,就置位一个“通信故障”寄存器,并在上位机界面弹窗。千万不要无限制重试,否则PLC侧刚恢复时,队列里全是要写的旧条码,反而把新数据顶掉了。
读取D寄存器时也建议加超时。TCP连接上的Read操作如果不设置超时,PLC死机时你的线程会永远挂在那里。设个3000毫秒的超时,超时就断线重连,这种处理在产线上能扛很多意外。
4.4 把“支持基恩士、三菱PLC MC协议”做成可扩展结构
代码写多了你会发现,所谓的“支持多品牌”,本质上就是抽象接口。我把接收设备和写入设备各定义成接口:
- 接收设备接口:
Connect()、Disconnect()、OnDataReceived - PLC写入接口:
Connect()、ReadWords()、WriteWords()、ReadBit()、WriteBit()
这样基恩士扫码枪是三菱MC协议之外的一类实现,将来换成康耐视、海康读码器,只是换一个接收端实现类;将来PLC换成汇川、信捷甚至西门子,也只是换一个PLC实现类。这个项目叫“支持基恩士、三菱PLC MC协议”,但骨架上你不能只留死代码,否则下一个项目又得推倒重来。
5. 现场调试踩过的坑,每一条都是花钱买来的
5.1 基恩士扫码枪连不上的排查链路
先看网络层,再谈协议层,这是我一贯的排查顺序。扫码枪连不上的时候,我先ping一下扫码枪IP,能ping通再查端口;如果ping不通,百分之八九十是IP没配对或者扫码枪的网口没激活。如果用TCP调试工具连不上端口,检查扫码枪是不是被配置成了UDP模式,或者服务端程序没起来。
另一个容易忽略的点:Windows防火墙。有些工控机上装的杀毒软件会把监听端口给过滤掉,导致扫码枪主动连接被拒。这种情况在开发机上极少出现,因为开发机一般不跑那些安全软件,一部署到现场就出问题。排查时直接看Windows安全中心的“防火墙和网络保护”,把程序所在目录或监听端口加白名单。
5.2 MC协议读写超时,根因往往不在报文上
MC协议报文格式错了,拿到的通常是结束代码0xC051、0xC055这类错误,而不是超时。如果你遇到的是超时,也就是一直没响应,那问题多半在网络通道上,而不是报文本身。
具体来说,我先用Network Analyzer(Wireshark)抓包,看请求帧到底有没有发出去,以及PLC的回复帧有没有回来。能看到回复但程序超时,就是响应解析的字节位置读错了;完全看不到回复,那就是连接断了或者PLC侧没把以太网通信参数配好。
MC协议的TCP连接其实是长连接。如果上位机代码里每次读写都new一个TcpClient,不仅慢,还容易触发PLC的连接数限制,因为每断开一次,PLC侧保留的socket资源要过一段时间才释放。正确做法是初始化一次连接,后面所有指令复用同一个NetworkStream,再加一个断线重连机制。
5.3 触摸屏和上位机同时连PLC的坑
三菱Q系列的内置以太网口或者以太网模块,同时允许的MC协议连接数是有限制的,常见是8个或者16个。一个昆仑通泰触摸屏占一个连接,上位机占一个连接,你还开着GX Works2在线监控、GX Simulator模拟器、MC协议调试工具,这些加起来很容易把连接数占满。
解决办法是:在线调试的时候,把不用的连接全部断开。尤其是GX Works2的在线监视,它不只是占一个通道,有时还会影响PLC的响应优先级。把这些都关掉后再测试上位机通信,通常就正常了。
另外一个网络规划问题:如果触摸屏、PLC、上位机在同一个网段,IP分配要避免冲突。我在一个项目里就遇到过触摸屏的IP和上位机的虚拟网卡IP撞车,导致PLC的数据时通时断。最简单的办法是给PLC固定一个专门IP段,比如192.168.3.x,触摸屏和上位机各占一个固定IP,统一写到图纸备注里。
5.4 PLC程序里没有对应的处理逻辑,功能还是不通
这是最容易忽略的一层。上位机把条码写进D100了,M100也置位了,但PLC程序里如果没有“当M100为ON时,把D100的字符串解析并执行判断”的逻辑,外面看起来依然是不通的。
我一般给PLC程序留的接口非常明确:D100开始存条码ASCII值,M100上升沿表示“新条码到达”,M101由PLC程序置位表示“数据已处理”,上位机写完后等待M101,收到后清掉M100并复位M101。这样两边各管各的,不会互相把对方的状态覆盖掉。
最后再分享一个小技巧
我在做MC协议调试时,除了正常的业务程序,还会留一个“协议自检”功能:上位机上做个按钮,手动发一条固定的批量读取指令,比如读取D0和D1,然后把响应帧的十六进制打印在日志里。这个功能看起来不起眼,但它能帮你把“网络问题”和“业务数据问题”瞬间分开。以后现场再报通信故障,先点一下自检:能读到说明网络和协议都是好的,那就是上层逻辑的问题;不能读到,再抓包查通道。
基恩士扫码枪那侧也可以做一个等价的自检:上位机界面显示当前收到的最后一条原始字符串原文,而不是解析后的条码。这样一旦出现条码不对,你能立刻判断是扫码枪吐出来的就不对,还是协议转换过程改坏了。产线调试这件事,尽早把问题隔离在某一段,比什么花哨的功能都管用。
本文还有配套的精品资源,点击获取