ModBus-RTU详解

2023-11-16

Modbus 一个工业上常用的通讯协议、一种通讯约定。Modbus协议包括RTU、ASCII、TCP。其中MODBUS-RTU最常用,比较简单,在单片机上很容易实现。虽然RTU比较简单,但是看协议资料、手册说得太专业了,起初很多内容都很难理解。
    所谓的协议是什么?就是互相之间的约定嘛,如果不让别人知道那就是暗号。现在就来定义一个新的最简单协议。例如,

协议: “A” --“LED灭”
       “B” --“报警”
       “C” --“LED亮”
单片机接收到“A”控制一个LED灭,单片机接收到“B”控制报警,单片机接收到“A”控制一个LED亮。那么当收到对应的信息就执行相应的动作,这就是协议,很简单吧。

  先来简单分析一条MODBUS-RTU报文,例如:01  06  00 01  00 17  98 04 
    01             06            00 01           00 17          98 04 
  从机地址        功能号          数据地址          数据         CRC校验

这一串数据的意思是:把数据 0x0017(十进制23) 写入 1号从机地址 0x0001数据地址。

先弄明白下面的东西。

1、报文
   一个报文就是一帧数据,一个数据帧就一个报文: 指的是一串完整的指令数据,就像上面的一串数据。

2、CRC校验
意义:例如上面的  98 04  是它前面的数据(01 06 00 01 00 17)通过一算法(见附录2,很简单的)计算出来的结果,其实就像是计算累加和那样。(累加和:就是010600010017加起来的值,然后它的算法就是加法)。
作用:在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确。比如主机发出01 06 00 01 00 17 98 04,那么从机接收到后要根据01 06 00 01 00 17 再计算CRC校验值,从机判断自己计算出来的CRC校验是否与接收的CRC校验(98 04主机计算的)相等,如果不相等那么说明数据传输有错误这些数据不能要。

3、功能号
  意义:modbus 定义。见附录1。
  作用:指示具体的操作。

MODBUS-RTU
一、一个报文分析
先声明下我们的目的,我们是要两个设备通讯,用的是MODBUS协议。上面简单介绍了:“报文”“CRC校验”“功能号”。

在单片机中拿出一部分内存(RAM)进行两个设备通讯,例如:

附件

数组后面的注释,说明
OX[20]   代表是输出线圈,用功能码 0x01,0x05,0x0F 访问, 开头地址是 0 (这个后续说明)
IX[20]    代表是输入线圈,用功能码 0x02 访问,             开头地址是 1 (这个后续说明)
另外两个一样的道理。
注意:所谓的“线圈”“寄存器”就是“位变量”“16位变量”,不要被迷惑。之所以称“线圈”我觉得应该是对于应用的设备,MODBUS协议是专门针对485总线设备(例PLC)开发的。

1、主机对从机写数据操作
如果单片机接收到一个报文那么就对报文进行解析执行相应的处理,如上面报文:
    01             06            00 01           00 17          98 04 
  从机地址        功能号          数据地址          数据         CRC校验

假如本机地址是 1 ,那么单片机接收到这串数据根据数据计算CRC校验判断数据是否正确,如果判断数据无误,则结果是:
            HoldDataReg[1]  =  0x0017;
MODBUS主机就完成了一次对从机数据的写操作,实现了通讯。

2、主机对从机读数据操作
主机进行读HoldDataReg[1] 操作,则报文是:
    01             03            00 01           00 01          D5 CA 
 从机地址        功能号          数据地址      读取数据个数       CRC校验
那么单片机接收到这串数据根据数据计算CRC校验判断数据是否正确,如果判断数据无误,则结果是:返回信息给主机,返回的信息也是有格式的:
返回内容:  
    01         03            02             0017          F8 4A
  从机地址   功能号     数据字节个数    两个字节数据    CRC校验
MODBUS主机就完成了一次对从机数据的读操作,实现了通讯。


二、MODBUS报文模型

以上了解到了MODBUS的一帧报文是如何通讯的,其实每个报文的格式都基本一样的。
 

附件

这里两个缩略词以前不知道,但是现在要明白指的是什么,“ADU”“PDU”
ADU: 应用数据单元
PDU: 协议数据单元

三、MODBUS数据模型
 

附件

附件

  


四、MODBUS事务处理
  下列状态图描述了在服务器侧MODBUS事务处理的一般处理过程。
           

附件


五、MODBUS请求与响应
  看MODBUS协议手册,中文第 10 页开始,英文第 24 页开始。手册非常详细举例说明了MODBUS协议各个功能号的请求与响应。 



                                                         modbus协议在单片机上实现过程


MODBUS 任务处理函数
 

附件


函数中,RcvBuf 为串口接收缓冲区,如果接收了一个报文则,RcvBuf[0] 为从机地址,RcvBuf[0] 为MODBUS功能号。根据功能号做出响应,而具体的操作根据功能号在各自的函数中执行,相当于解析接收到的数据。

附录1:MODBUS-RTU功能码
 最常用功能码:
 下面“线圈”“寄存器”其实分别直的就是“位变量”“16位变量”
        01 (0x01)        读线圈 
        02 (0x02)        读离散量输入
        03 (0x03)        读保持寄存器
        04(0x04)         读输入寄存器
        05 (0x05)        写单个线圈 
        06 (0x06)        写单个寄存器
        15 (0x0F)        写多个线圈 
        16 (0x10)        写多个寄存器
 

附件


附录2:CRC Generation
 

附件

 

帧检验域:循环冗余校验 (CRC)

在RTU 模式包含一个对全部报文内容执行的,基于循环冗余校验 (CRC - Cyclical Redundancy Checking) 算法的错误检验域。

CRC 域检验整个报文的内容。不管报文有无奇偶校验,均执行此检验。

CRC 包含由两个8位字节组成的一个16位值。  

CRC 域作为报文的最后的域附加在报文之后。计算后,首先附加低字节,然后是高字节。CRC 高字节为报文发送的最后一个子节。

附加在报文后面的CRC 的值由发送设备计算。接收设备在接收报文时重新计算 CRC 的值,并将计算结果于实际接收到的CRC 值相比较。如果两个值不相等,则为错误。

CRC 的计算,开始对一个16位寄存器预装全1。 然后将报文中的连续的8位子节对其进行后续的计算。只有字符中的8个数据位参与生成CRC 的运算,起始位,停止位和校验位不参与 CRC 计算。

CRC 的生成过程中, 每个 8–位字符与寄存器中的值异或。然后结果向最低有效位(LSB)方向移动(Shift) 1位,而最高有效位(MSB)位置充零。 然后提取并检查 LSB:如果LSB 为1, 则寄存器中的值与一个固定的预置值异或;如果LSB 为 0, 则不进行异或操作。

这个过程将重复直到执行完8次移位。完成最后一次(第8次)移位及相关操作后,下一个8位字节与寄存器的当前值异或,然后又同上面描述过的一样重复8次。当所有报文中子节都运算之后得到的寄存器中的最终值,就是CRC。

当CRC 附加在报文之后时,首先附加低字节,然后是高字节。

CRC 算法如下:

private bool CheckResponse(byte[] response){ //Perform a basic CRC check: byte[] CRC = new byte[2]; GetCRC(response, ref CRC); if (CRC[0] == response[response.Length - 2] &;&; CRC[1] == response[response.Length - 1]) return true; else return false;}private void GetCRC(byte[] message, ref byte[] CRC){ //Function expects a modbus message of any length as well as a 2 byte CRC array in which to //return the CRC values: ushort CRCFull = 0xFFFF; byte CRCHigh = 0xFF, CRCLow = 0xFF; char CRCLSB; for (int i = 0; i < (message.Length) - 2; i++) { CRCFull = (ushort)(CRCFull ^ message[i]); for (int j = 0; j < 8; j++) { CRCLSB = (char)(CRCFull &; 0x0001); CRCFull = (ushort)((CRCFull >> 1) &; 0x7FFF); if (CRCLSB == 1) CRCFull = (ushort)(CRCFull ^ 0xA001); } } CRC[1] = CRCHigh = (byte)((CRCFull >> 8) &; 0xFF); CRC[0] = CRCLow = (byte)(CRCFull &; 0xFF);}

 

帧描述 (如下图所示) :

Modbus RTU 通信工具设计_ASP.NET

图3:RTU 报文帧

注意:Modbus  RTU 帧最大为256字节。

 


Modbus通讯协议及编程

一、Modbus 协议简介

 

 Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。

 

 此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

 

 当在一Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus协议发出。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误检测的方法。

1、在Modbus网络上转输

 标准的Modbus口是使用一RS-232C兼容串行接口,它定义了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。

 

 控制器通信使用主—从技术,即仅一设备(主设备)能初始化传输(查询)。其它设备(从设备)根据主设备查询提供的数据作出相应反应。典型的主设备:主机和可编程仪表。典型的从设备:可编程控制器。

 

 主设备可单独和从设备通信,也能以广播方式和所有从设备通信。如果单独通信,从设备返回一消息作为回应,如果是以广播方式查询的,则不作任何回应。Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、一错误检测域。

 

 从设备回应消息也由Modbus协议构成,包括确认要行动的域、任何要返回的数据、和一错误检测域。如果在消息接收过程中发生一错误,或从设备不能执行其命令,从设备将建立一错误消息并把它作为回应发送出去。

2、在其它类型网络上转输

 在其它网络上,控制器使用对等技术通信,故任何控制都能初始和其它控制器的通信。这样在单独的通信过程中,控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。

 

 在消息位,Modbus协议仍提供了主—从原则,尽管网络通信方法是“对等”。如果一控制器发送一消息,它只是作为主设备,并期望从从设备得到回应。同样,当控制器接收到一消息,它将建立一从设备回应格式并返回给发送的控制器。

3、查询—回应周期

(1)查询

 查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。

(2)回应

 如果从设备产生一正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据:象寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。

二、两种传输方式

 控制器能设置为两种传输模式(ASCII或RTU)中的任何一种在标准的Modbus网络通信。用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。

ASCII模式

:

地址

功能代码

数据数量

数据1

...

数据n

LRC高字节

LRC低字节

回车

换行

RTU模式

地址

功能代码

数据数量

数据1

...

数据n

CRC低字节

CRC高字节

 所选的ASCII或RTU方式仅适用于标准的Modbus网络,它定义了在这些网络上连续传输的消息段的每一位,以及决定怎样将信息打包成消息域和如何解码。

 在其它网络上(象MAP和Modbus Plus)Modbus消息被转成与串行传输无关的帧。

1、ASCII模式

 当控制器设为在Modbus网络上以ASCII(美国标准信息交换代码)模式通信,在消息中的每个8Bit字节都作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。

 代码系统

·  十六进制,ASCII字符0...9,A...F

·  消息中的每个ASCII字符都是一个十六进制字符组成

每个字节的位

·  1个起始位

·  7个数据位,最小的有效位先发送

·  1个奇偶校验位,无校验则无

·  1个停止位(有校验时),2个Bit(无校验时)

 错误检测域

·            LRC(纵向冗长检测)

2、RTU模式

 当控制器设为在Modbus网络上以RTU(远程终端单元)模式通信,在消息中的每个8Bit字节包含两个4Bit的十六进制字符。这种方式的主要优点是:在同样的波特率下,可比ASCII方式传送更多的数据。

代码系统

·  8位二进制,十六进制数0...9,A...F

·  消息中的每个8位域都是一个两个十六进制字符组成

·  每个字节的位

·  1个起始位

·  8个数据位,最小的有效位先发送

·  1个奇偶校验位,无校验则无

·  1个停止位(有校验时),2个Bit(无校验时)

错误检测域

·  CRC(循环冗长检测)

三、Modbus消息帧

 两种传输模式中(ASCII或RTU),传输设备以将Modbus消息转为有起点和终点的帧,这就允许接收的设备在消息起始处开始工作,读地址分配信息,判断哪一个设备被选中(广播方式则传给所有设备),判知何时信息已完成。部分的消息也能侦测到并且错误能设置为返回结果。

1、ASCII帧

 使用ASCII模式,消息以冒号(:)字符(ASCII码 3AH)开始,以回车换行符结束(ASCII码 0DH,0AH)。

 

 其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不断侦测“:”字符,当有一个冒号接收到时,每个设备都解码下个域(地址域)来判断是否发给自己的。

 

 消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误。一个典型消息帧如下所示:

起始位

设备地址

功能代码

数据

LRC校验

结束符

1个字符

2个字符

2个字符

n个字符

2个字符

2个字符

图2 ASCII消息帧

2、RTU帧

 使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1-T2-T3-T4所示)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络设备不断侦测网络总线,包括停顿间隔时间内。当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。

 

 整个消息帧必须作为一连续的流转输。如果在帧完成之前有超过1.5个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将导致一个错误,因为在最后的CRC域的值不可能是正确的。一典型的消息帧如下所示:

 

起始位

设备地址

功能代码

数据

CRC校验

结束符

T1-T2-T3-T4

8Bit

8Bit

n个8Bit

16Bit

T1-T2-T3-T4

图3 RTU消息帧

3、地址域

 消息帧的地址域包含两个字符(ASCII)或8Bit(RTU)。可能的从设备地址是0...247 (十进制)。单个设备的地址范围是1...247。主设备通过将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时,它把自己的地址放入回应的地址域中,以便主设备知道是哪一个设备作出回应。

 

 地址0是用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高水准的网络,广播可能不允许或以其它方式代替。

4、如何处理功能域

 消息帧中的功能代码域包含了两个字符(ASCII)或8Bits(RTU)。可能的代码范围是十进制的1...255。当然,有些代码是适用于所有控制器,有此是应用于某种控制器,还有些保留以备后用。

 

 当消息从主设备发往从设备时,功能代码域将告之从设备需要执行哪些行为。例如去读取输入的开关状态,读一组寄存器的数据内容,读从设备的诊断状态,允许调入、记录、校验在从设备中的程序等。

 

 当从设备回应时,它使用功能代码域来指示是正常回应(无误)还是有某种错误发生(称作异议回应)。对正常回应,从设备仅回应相应的功能代码。对异议回应,从设备返回一等同于正常代码的代码,但最重要的位置为逻辑1。

 例如:一从主设备发往从设备的消息要求读一组保持寄存器,将产生如下功能代码:

0 0 0 0 0 0 1 1 (十六进制03H)

 对正常回应,从设备仅回应同样的功能代码。对异议回应,它返回:

1 0 0 0 0 0 1 1 (十六进制83H)

 除功能代码因异议错误作了修改外,从设备将一独特的代码放到回应消息的数据域中,这能告诉主设备发生了什么错误。

 主设备应用程序得到异议的回应后,典型的处理过程是重发消息,或者诊断发给从设备的消息并报告给操作员。

5、数据域

 数据域是由两个十六进制数集合构成的,范围00...FF。根据网络传输模式,这可以是由一对ASCII字符组成或由一RTU字符组成。

 从主设备发给从设备消息的数据域包含附加的信息:从设备必须用于进行执行由功能代码所定义的所为。这包括了象不连续的寄存器地址,要处理项的数目,域中实际数据字节数。

 例如,如果主设备需要从设备读取一组保持寄存器(功能代码03),数据域指定了起始寄存器以及要读的寄存器数量。如果主设备写一组从设备的寄存器(功能代码10十六进制),数据域则指明了要写的起始寄存器以及要写的寄存器数量,数据域的数据字节数,要写入寄存器的数据。

 如果没有错误发生,从从设备返回的数据域包含请求的数据。如果有错误发生,此域包含一异议代码,主设备应用程序可以用来判断采取下一步行动。

 在某种消息中数据域可以是不存在的(0长度)。例如,主设备要求从设备回应通信事件记录(功能代码0B十六进制),从设备不需任何附加的信息。

6、错误检测域

 标准的Modbus网络有两种错误检测方法。错误检测域的内容视所选的检测方法而定。

ASCII

 当选用ASCII模式作字符帧,错误检测域包含两个ASCII字符。这是使用LRC(纵向冗长检测)方法对消息内容计算得出的,不包括开始的冒号符及回车换行符。LRC字符附加在回车换行符前面。

RTU

 当选用RTU模式作字符帧,错误检测域包含一16Bits值(用两个8位的字符来实现)。错误检测域的内容是通过对消息内容进行循环冗长检测方法得出的。CRC域附加在消息的最后,添加时先是低字节然后是高字节。故CRC的高位字节是发送消息的最后一个字节。

7、字符的连续传输

 当消息在标准的Modbus系列网络传输时,每个字符或字节以如下方式发送(从左到右):

 最低有效位...最高有效位

 使用ASCII字符帧时,位的序列是:

 有奇偶校验

启始位

1

2

3

4

5

6

7

奇偶位

停止位

 无奇偶校验

启始位

1

2

3

4

5

6

7

停止位

停止位

图4. 位顺序(ASCII)

 使用RTU字符帧时,位的序列是:

 有奇偶校验

启始位

1

2

3

4

5

6

7

8

奇偶位

停止位

 无奇偶校验

启始位

1

2

3

4

5

6

7

8

停止位

停止位

图4. 位顺序(RTU)

四、错误检测方法

 标准的Modbus串行网络采用两种错误检测方法。奇偶校验对每个字符都可用,帧检测(LRC或CRC)应用于整个消息。它们都是在消息发送前由主设备产生的,从设备在接收过程中检测每个字符和整个消息帧。

 用户要给主设备配置一预先定义的超时时间间隔,这个时间间隔要足够长,以使任何从设备都能作为正常反应。如果从设备测到一传输错误,消息将不会接收,也不会向主设备作出回应。这样超时事件将触发主设备来处理错误。发往不存在的从设备的地址也会产生超时。

1、奇偶校验

 用户可以配置控制器是奇或偶校验,或无校验。这将决定了每个字符中的奇偶校验位是如何设置的。

 如果指定了奇或偶校验,“1”的位数将算到每个字符的位数中(ASCII模式7个数据位,RTU中8个数据位)。例如RTU字符帧中包含以下8个数据位:

1 1 0 0 0 1 0 1

 整个“1”的数目是4个。如果便用了偶校验,帧的奇偶校验位将是0,便得整个“1”的个数仍是4个。如果便用了奇校验,帧的奇偶校验位将是1,便得整个“1”的个数是5个。

 如果没有指定奇偶校验位,传输时就没有校验位,也不进行校验检测。代替一附加的停止位填充至要传输的字符帧中。

 

2、LRC检测

 

 使用ASCII模式,消息包括了一基于LRC方法的错误检测域。LRC域检测了消息域中除开始的冒号及结束的回车换行号外的内容。

 LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中,接收设备在接收消息的过程中计算LRC,并将它和接收到消息中LRC域中的值比较,如果两值不等,说明有错误。

 LRC方法是将消息中的8Bit的字节连续累加,丢弃了进位。

 LRC简单函数如下:

static unsigned char LRC(auchMsg,usDataLen)

unsigned char *auchMsg ; /* 要进行计算的消息 */

unsigned short usDataLen ; /* LRC 要处理的字节的数量*/

{ unsigned char uchLRC = 0 ; /* LRC 字节初始化 */

while (usDataLen--) /* 传送消息 */

uchLRC += *auchMsg++ ; /* 累加*/

return ((unsigned char)(-((char_uchLRC))) ;

}

3、CRC检测

 使用RTU模式,消息包括了一基于CRC方法的错误检测域。CRC域检测了整个消息的内容。

 CRC域是两个字节,包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到的CRC域中的值比较,如果两值不同,则有误。

 CRC是先调入一值是全“1”的16位寄存器,然后调用一过程将消息中连续的8位字节各当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效,起始位和停止位以及奇偶校验位均无效。

 CRC产生过程中,每个8位字符都单独和寄存器内容相或(OR),结果向最低有效位方向移动,最高有效位以0填充。LSB被提取出来检测,如果LSB为1,寄存器单独和预置的值或一下,如果LSB为0,则不进行。整个过程要重复8次。在最后一位(第8位)完成后,下一个8位字节又单独和寄存器的当前值相或。最终寄存器中的值,是消息中所有的字节都执行之后的CRC值。

 CRC添加到消息中时,低字节先加入,然后高字节。

 

 

 

 

MODBUS通讯协议及编程【二】

  ModBus通讯协议分为RTU协议和ASCII协议,我公司的多种仪表都采用ModBus RTU通讯协议,如:CH2000智能电力监测仪、CH2000M电力参数采集模块、巡检表、数显表、光柱数显表等。下面就ModBus RTU协议简要介绍如下:

一、通讯协议

(一)、通讯传送方式:
   通讯传送分为独立的信息头,和发送的编码数据。以下的通讯传送方式定义也与MODBUS RTU通讯规约相兼容:

编 码

8位二进制

起始位

1位

数据位

8位

奇偶校验位

1位(偶校验位)

停止位

1位

错误校检

CRC(冗余循环码)

初始结构 = ≥4字节的时间 
地址码 = 1 字节
功能码 = 1 字节
数据区 = N 字节
错误校检 = 16位CRC码 
结束结构 = ≥4字节的时间


  地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。并且每个从机都有具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。

  功能码:通讯传送的第二个字节。ModBus通讯规约定义功能号为1到127。本仪表只利用其中的一部分功能码。作为主机请求发送,通过功能码告诉从机执行什么动作。作为从机响应,从机发送的功能码与从主机发送来的功能码一样,并表明从机已响应主机进行操作。如果从机发送的功能码的最高位为1(比如功能码大与此同时127),则表明从机没有响应操作或发送出错。

  数据区:数据区是根据不同的功能码而不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。

   CRC码:二字节的错误检测码。

(二)、通讯规约:

   当通讯命令发送至仪器时,符合相应地址码的设备接通讯命令,并除去地址码,读取信息,如果没有出错,则执行相应的任务;然后把执行结果返送给发送者。返送的信息中包括地址码、执行动作的功能码、执行动作后结果的数据以及错误校验码。如果出错就不发送任何信息。

1.信息帧结构

地址码

功能码

数据区

错误校验码

8位

8位

N × 8位

16位

  地址码:地址码是信息帧的第一字节(8位),从0到255。这个字节表明由用户设置地址的从机将接收由主机发送来的信息。每个从机都必须有唯一的地址码,并且只有符合地址码的从机才能响应回送。当从机回送信息时,相当的地址码表明该信息来自于何处。

   功能码:主机发送的功能码告诉从机执行什么任务。表1-1列出的功能码都有具体的含义及操作。

代码

含义

操作

03

读取数据

读取当前寄存器内一个或多个二进制值

06

重置单一寄存器

把设置的二进制值写入单一寄存器

  数据区:数据区包含需要从机执行什么动作或由从机采集的返送信息。这些信息可以是数值、参考地址等等。例如,功能码告诉从机读取寄存器的值,则数据区必需包含要读取寄存器的起始地址及读取长度。对于不同的从机,地址和数据信息都不相同。

  错误校验码:主机或从机可用校验码进行判别接收信息是否出错。有时,由于电子噪声或其它一些干扰,信息在传输过程中会发生细微的变化,错误校验码保证了主机或从机对在传送过程中出错的信息不起作用。这样增加了系统的安全和效率。错误校验采用CRC-16校验方法。

注:信息帧的格式都基本相同:地址码、功能码、数据区和错误校验码。

2.错误校验

   冗余循环码(CRC)包含2个字节,即16位二进制。CRC码由发送设备计算,放置于发送信息的尾部。接收信息的设备再重新计算接收到信息的 CRC码,比较计算得到的CRC码是否与接收到的相符,如果两者不相符,则表明出错。

  CRC码的计算方法是,先预置16位寄存器全为1。再逐步把每8位数据信息进行处理。在进行CRC码计算时只用8位数据位,起始位及停止位,如有奇偶校验位的话也包括奇偶校验位,都不参与CRC码计算。

   在计算CRC码时,8位数据与寄存器的数据相异或,得到的结果向低位移一字节,用0填补最高位。再检查最低位,如果最低位为1,把寄存器的内容与预置数相异或,如果最低位为0,不进行异或运算。

   这个过程一直重复8次。第8次移位后,下一个8位再与现在寄存器的内容相相异或,这个过程与以上一样重复8次。当所有的数据信息处理完后,最后寄存器的内容即为CRC码值。CRC码中的数据发送、接收时低字节在前。

   计算CRC码的步骤为:

·  预置16位寄存器为十六进制FFFF(即全为1)。称此寄存器为CRC寄存器;

·  把第一个8位数据与16位CRC寄存器的低位相异或,把结果放于CRC寄存器;

·  把寄存器的内容右移一位(朝低位),用0填补最高位,检查最低位;

·  如果最低位为0:重复第3步(再次移位); 如果最低位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;

·  重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;

·  重复步骤2到步骤5,进行下一个8位数据的处理;

·  最后得到的CRC寄存器即为CRC码。

3.功能码03,读取点和返回值:

  仪表采用Modbus RTU通讯规约,利用通讯命令,可以进行读取点(“保持寄存器”) 或返回值(“输入寄存器” )的操作。保持和输入寄存器都是16位(2字节)值,并且高位在前。这样用于仪表的读取点和返回值都是2字节。一次最多可读取寄存器数是60。由于一些可编程控制器不用功能码03,所以功能码03被用作读取点和返回值。从机响应的命令格式是从机地址、功能码、数据区及CRC码。数据区中的寄存器数据都是每两个字节高字节在前。

4.功能码06,单点保存

  主机利用这条命令把单点数据保存到仪表的存储器。从机也用这个功能码向主机返送信息。

二、编程举例

  下面是一个用VC编写的ModBus RTU通讯的例子

(一)、通讯口设置

DCB dcb;
hCom=CreateFile("COM1",
     GENERIC_READ|GENERIC_WRITE,
     0,
     NULL,
     OPEN_EXISTING,
     0,
     NULL);
if(hCom==INVALID_HANDLE_VALUE)
{
  MessageBox("createfile error,error");
}
BOOL error=SetupComm(hCom,1024,1024);
if(!error)
  MessageBox("setupcomm error");
error=GetCommState(hCom,&dcb);
if(!error)
  MessageBox("getcommstate,error");
dcb.BaudRate=2400;
dcb.ByteSize=8;

dcb.Parity=EVENPARITY;//NOPARITY;
dcb.StopBits=ONESTOPBIT;

error=SetCommState(hCom,&dcb);

(二)、CRC校验码计算

UINT crc
void calccrc(BYTE crcbuf)
{
BYTE i;

crc=crc ^ crcbuf;
for(i=0;i<8;i++)
{
BYTE TT;
TT=crc&1;
crc=crc>>1;
crc=crc&0x7fff;
if (TT==1)
crc=crc^0xa001;
crc=crc&0xffff;
}
}

(三)、数据发送

zxaddr=11;//读取地址为11的巡检表数据
zxnum=10;//读取十个通道的数据

writebuf2[0]=zxaddr;
writebuf2[1]=3;
writebuf2[2]=0;
writebuf2[3]=0;
writebuf2[4]=0;
writebuf2[5]=zxnum;
crc=0xffff;
calccrc(writebuf2[0]);
calccrc(writebuf2[1]);
calccrc(writebuf2[2]);
calccrc(writebuf2[3]);
calccrc(writebuf2[4]);
calccrc(writebuf2[5]);

writebuf2[6]=crc & 0xff;
writebuf2[7]=crc/0x100;
WriteFile(hCom,writebuf2,8,&comnum,NULL);

(四)、数据读取

ReadFile(hCom,writebuf,5+zxnum*2,&comnum,NULL);//读取zxnum个通道数据
可增加错误处理程序,如地址码错误、CRC码错误判断、通讯故障处理等。

 

 

 

 

 

 

 CRC简单函数如下:

unsigned short CRC16(puchMsg, usDataLen)

unsigned char *puchMsg ; /* 要进行CRC校验的消息 */

unsigned short usDataLen ; /* 消息中字节数 */

{

unsigned char uchCRCHi = 0xFF ; /* 高CRC字节初始化 */

unsigned char uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */

unsigned uIndex ; /* CRC循环中的索引 */

while (usDataLen--) /* 传输消息缓冲区 */

{

uIndex = uchCRCHi ^ *puchMsgg++ ; /* 计算CRC */

uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex} ;

uchCRCLo = auchCRCLo[uIndex] ;

}

return (uchCRCHi << 8 | uchCRCLo) ;

}

/* CRC 高位字节值表 */

static unsigned char auchCRCHi[] = {

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

} ;

/* CRC低位字节值表*/

static char auchCRCLo[] = {

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

} ;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ModBus-RTU详解 的相关文章

  • 大二上详细计划(量化到周)

    目录 前言 统筹安排 每周量化 每周进度 总体 花絮 前言 1 思来想去 决定对大二上的四个月进行一个安排 量化每周任务 具体到一本书看多少页 一套视频学多少 每个节点任务完成度 2 我们都是思想上的巨人 行动上的矮子 各种各样的欲望会阻碍
  • unity 根据顶点位置变换顶点颜色

    using System Collections using System Collections Generic using UnityEngine public class HeightCOlor MonoBehaviour publi
  • 服务器虚拟化 —— 集群服务器 —— 分布式 —— 云服务

    一 服务器虚拟化技术 是下面集群服务器 云服务的技术基石 将服务器的物理资源抽象成逻辑资源 让一台服务器变成几台甚至上百台 很少见啊 相互隔离的虚拟服务器 我们不再受限于物理上的界限 而是让CPU 内存 磁盘 I O等硬件变成可以动态管理的
  • 动物识别专家系统(Java实现已开源)

    动物识别专家系统 产生式系统 问题定义 系统实现 规则 事实和产生式的表示 类的设计 匹配顺序 推理机 结果演示 源代码 产生式系统 规则库 规则库是用于描述相应领域内知识的产生式集合 它是产生式系统求解问题的基础 其中对领域知识表达的完整
  • 安卓子线程内存问题——有结论

    问题描述 有一套C 库 通过JNI被安卓应用调用 应用中在主线程 UI现场 调用一函数正常 在子线程中调用该函数会导致APP崩溃 APP崩溃时报错信息如下 E libsigchain exiting due to SIG DFL handl
  • 隐私政策

    本应用尊重并保护所有使用服务用户的个人隐私权 为了给您提供更准确 更有个性化的服务 本应用会按照本隐私权政策的规定使用和披露您的个人信息 但本应用将以高度的勤勉 审慎义务对待这些信息 除本隐私权政策另有规定外 在未征得您事先许可的情况下 本
  • 马原刷题工具

    选择题库文件word后 就能开始刷题了 导入题库时会自动生成一个docx文件和json文件 想要重新导入就删除json文件 题库下载 点我下载 要安装的库 pywin32 python docx author Bre Athy contac
  • 2020这一年,我完成了这几件大事

    2020这一年 我完成了这几件大事 1 感情 拥有了余生的合伙人 2 工作 找到了自己喜欢的方向 一个长处 3 生活 走走停停 4 读书 兴之所至 1 感情 拥有了余生的合伙人 1 3 关键词 殇 4 6 关键词 静 7 9 关键词 安 1
  • java 判断电脑盘符是否存在

    public static void lkm FileSystemView fileSystemView FileSystemView getFileSystemView 获取FileSystemView对象 File roots File
  • 怎么把ipad中超大文件传输到电脑

    遇到了一个问题就是ipad文件中有几个超大压缩包需要传到电脑 想着上传到网盘在下载不是美滋滋 还特地借到了会员 为了上传大文件 但是网盘就很过分 会员在ipad端上传超过4g的文件也不允许 电脑端就没得问题 很无奈 然后问了度娘 给的各种解
  • 悲剧的山寨采用的新芯片资料汇总(更新Rk3066)

    芯片名称 基友公司 上市前宣传主频 量产机最高主频 最高主频 GPU 备注 Rk3066 原道 酷比魔方 1 4GHz 2 1 6GHz 2 1 6GHz 2 Mali 400MP4 266MHz 旧固件 Mali 400MP4 399MH
  • Java List<Map<String, Object>> 排序

    name key值 List
  • 关于海康,宇视,天地伟业摄像头调试

    最近在项目中需要读取摄像机的内容 现有的摄像机有海康威视 宇视 和天地伟业三家的摄像机 一开始 天地伟业和宇视都给了demo 即html和css代码 是可以读取视频流的 但是海康威视没有给demo 只是告诉我们可以通过vlc来读取视频流的内
  • 2022人生除生死外,无大事——一边治愈,一边前行!

    今天看完了 人生大事 这个电影 灵动的小姑娘 痞帅的朱一龙 一个殡葬师的生活在这个小姑娘的闯入后 开始有了波澜 活力 他逐渐找到了生活的意义和方向 挺好评的一部电影 虽然是关于离别 死亡 却是在很欢快嘈杂的情境下穿插着的 有泪有笑有温情 人
  • Java 集合根据条件进行拆分

    创建测试集合 List
  • ModBus-RTU详解

    Modbus 一个工业上常用的通讯协议 一种通讯约定 Modbus协议包括RTU ASCII TCP 其中MODBUS RTU最常用 比较简单 在单片机上很容易实现 虽然RTU比较简单 但是看协议资料 手册说得太专业了 起初很多内容都很难理
  • wps(word)复制过来的文字一行字数不足却自动换行解决办法

    问题描述 在wps或者word里粘贴网页上复制过来的文字常常会出现每行字数不足换行要求却提前换行的情况 如下图所示 问题分析 出现这种情况主要是从网页上复制过来的文字保留了原网站的段落标记 手动换行符 打开显示段落标记 出现向下的箭头符号就
  • 没有权限删除文件

    通过远程发版时 有可能会没有权限删除文件 如下解决方法 1 将user 用户切换root 用户 sudo su root 该方法不一定成功 因为有可能设置权限你不能切换 但成功以后一劳永逸 当方法1没有成功时 采用如下方法 2 将你所操作的
  • Json的float单精度浮点数类型支持Can't assign value '11.88' (type System.Double) to type System.Single

    今天遇到个问题Can t assign value 11 88 type System Double to type System Single litjson不支持单精度浮点数float 只用修改JsonMapper cs脚本就可以 一共
  • 通过lombok减少重复劳动

    lombok 是什么 lombok是一个java开发工具 能帮助我们减少大量的重复劳动 lombok能帮助我们做什么 lombok提供了大量的注解 只要添加了这些注解 lombok就能自动完成很多代码 举个例子 我们在写java的POJO时

随机推荐

  • konisGraph学习。复杂查询优化记录

    最近有需求是查两个公司之间的投资关系 比如 a和b之间有哪些直接投资和间接投资 例如 a gt b a gt e gt b a gt c gt d gt b b gt f gt a 需求是查出7跳以内的ab之间的投资关系 v的标签是comp
  • inline内联函数的作用和几点注意

    一 关键字inline必须与函数定义体放在一起才能使函数成为内联 仅将inline放在函数声明前面不起任何作用 二 定义在类声明之中的成员函数将自动地成为内联函数 以上可参考 http blog 163 com jinyongming bl
  • 矩阵求导 两种布局 及常用基础公式

    一 矩阵求导的两种形式 刚开始看矩阵求导这一块的时候发现一模一样的两个公式 在不同地方结果老是不一样 给我看懵了好一阵子 后来才发现原来有两种布局模式 都是可以的 矩阵求导的布局方式有两种 分子布局和分母布局 下面以 小写字母 小写粗体字母
  • stm32晶振配置不一致导致 Invalid Rom Table 至芯片锁死解决方案

    参考博文 Invalid ROM Table原因及解决办法 STM32芯片锁死问题 概述 在调试STM32时 出现如下的现象 软件写好之后首次下载程序至芯片时成功 但程序未能正常运行 之后再下载程序时便报错 Invalid ROM Tabl
  • RPC 服务优雅停机、优雅重启,你的服务重启够优雅吗?

    关注公众号 1024个为什么 及时接收最新推送文章 背景 由于最近上线比较频繁 就遇到了一个小概率的数据不一致的问题 具体过程如下 很明显 是服务没有做到优雅停机 导致的两个服务的数据不一致 优雅停机 优雅重启 本质到底是什么 优雅重启涵盖
  • 【最简单】STM32+ESP8266+MQTT+EMQX完成数据上传和点灯环节

    首先要吐槽一点 那就是网络上的这方面资料杂七杂八 而且很多都是一模一样的 然后我作为一个学生 没有多大的本事 就是想实现最基本的功能 然后在此基础上慢慢拓展 所以那些十分庞大的工程或者一些IoT物联网平台的官方SDK对于我来说是比较难看懂的
  • js、vue、vue3.0、时间格式转换函数

    时间戳转换时间格式 时间戳转任意时间格式 调用方法 gt this getDate 1626137166000 m d H i this getDate 1626137166000 等等 getDate function date form
  • c++ string中的data()和c_str() 区别与联系

    c str 返回的是const char 类型的指针 无法修改指针指向字符串的值 data 有两种版本 一种返回的是const char 类型的指针 此时与上面的c str 用法完全一致 网上有说c str返回的会加上 0 可能不同版本不一
  • 电路基础(2)电阻电路的等效变换

    1 电路的等效变换 两端网络 任何一个复杂的电路 向外引出两个端钮 且从一个端子流入的电流等于从另一端子流出的电流 则称这一电路为二端网络 或一端口网络 两端电路等效 两个两端电路 端口具有相同的电压 电流关系 则称它们是等效的电路 两电路
  • WordPress BuddyPress 存在越权漏洞RCE(CVE-2021-21389)

    一 漏洞描述 BuddyPress 是一个用于构建社区站点的开源 WordPress 插件 在 7 2 1 之前的 5 0 0 版本的 BuddyPress 中 非特权普通用户可以通过利用 REST API 成员端点中的问题来获得管理员权限
  • 错误,调试

    有些也写在了开发工具 xcode中 http article ityran com archives 1143 1 编译iPad真机时 选择了 Architetures Standard armv6 BaseSDK iPhoneDevice
  • 神经网络算法的基本原理,神经网络算法都有哪些

    神经网络算法的三大类分别是 神经网络算法的三大类分别是 1 前馈神经网络 这是实际应用中最常见的神经网络类型 第一层是输入 最后一层是输出 如果有多个隐藏层 我们称之为 深度 神经网络 他们计算出一系列改变样本相似性的变换 各层神经元的活动
  • GeoServer发布地图服务并在OpenLayers中显示:shp的WMS服务和GeoTIFF的WTMS服务

    GeoServer发布的地图服务结构如下 必须创建一个工作区 该工作区下可以存放多组无关数据 也就是说 工作区的作用就类似于一个文件夹 仅仅用于分类 在工作区下添加数据存储 数据存储即一组相关数据 通常 一个地区的地图可能是单个文件构成 也
  • vim进阶

    首先晒一张我的vim截图 基本IDE有的功能都能实现了 虽然在日常工作里还是IDE用的多 但是作为一个开发者 少不了折腾的心 vim 作为与emacs齐名的编辑器 无需更多溢美之词 由于学习曲线陡峭 但是学会之人 无不表示其方便 vim操作
  • Docker 安装 MySQL、Redis、Nginx

    222 1 Portainer 安装 docker可视化 2 Docker 安装MySQL 3 Docker安装Redis 4 Docker安装 Nginx 1 Portainer 安装 docker可视化 docker run d p 8
  • C++多线程(三)事件

    事件 解决两个线程通信问题 要注意死锁 g hEvent CreateEvent NULL FALSE FALSE NULL WaitForSingleObject g hEvent INFINITE ResetEvent g hEvent
  • ubuntu18-opencv3.4.6移植到aarch64

    一 前提条件 需先安装好arm64相关的交叉编译器 安装好cmake gui 下载并解压opencv3 4 6 下载ippicv 2019 lnx intel64 general 20180723 tgz 二 配置cmake 进入openc
  • Unity初学者学习路线与建议

    Unity初学者学习路线与建议 目录 Unity初学者学习路线与建议 一 前言 二 我的学习建议 三 学习要点 一 前言 Holle大家好 先自我介绍一下 我现在大二 专业是数字媒体技术 方向是游戏制作 学的是Unity 和我同专业的另一个
  • Ubuntu 16.04安装MinGW32

    直接使用命令 sudo apt get install mingw32 会报错 Unable to locate package mingw32 解决办法如下 sudo gedit etc apt sources list 在sources
  • ModBus-RTU详解

    Modbus 一个工业上常用的通讯协议 一种通讯约定 Modbus协议包括RTU ASCII TCP 其中MODBUS RTU最常用 比较简单 在单片机上很容易实现 虽然RTU比较简单 但是看协议资料 手册说得太专业了 起初很多内容都很难理