0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Modbus RTU的设计与实现

CHANBAEK 来源:木南创智 作者:尹家军 2022-12-13 15:27 次阅读

前面我们已经详细讲解过Modbus协议栈的开发过程,并且利用协议栈封装了Modbus RTU主站和从站,Modbus TCP服务器与客户端,Modbus ASCII主站与从站应用。但在使用过程中,我们发现一些使用不便和受限的地方,所以我们就想要更新一下协议栈,主要是应用站的封装。

1 、存在的局限性

在原有的协议栈中,我们所封装的Modbus RTU主站是一个特定的主站,即它只是一个主站。在通常的应用中不会有什么问题,但在有些应用场合就会显现出它的局限性。

首先,作为一个特定的主站,带多个从站时,写从站的处理变的非常复杂,需要分辨不同的从站,不同的变量。当有多个端口时,还需要分辨不同的端口。

其次,作为一个特定的主站,带多个从站时,读从站的处理,即使是不同的端口也不能分辨相同站地址的从站。因为同一主站的解析函数是同一个,所以即使在不同端口也很难分辨,除非在解析前传递端口信息,这其实是将多余的信息传递到协议栈。这样做不但程序不够明晰也缺乏一般性。

最后,将所有的Modbus从站通讯都作为唯一的一个特定从站来处理,使得各部分混杂在一起,程序结构很不清晰,对象也不明确。

2 、更新设计

考虑到前述的局限性,我们将主站及其带访问的从站定义为通用的对象,而当我们在具体应用中使用时,再将其特例化为特定的主站和从站对象。

首先我们来考虑主站,原则上我们规划的每一个主站对象对应我们设备上的一个端口,那么在同一端口下,也就是在一个特定主站下,我们可以定义多个地址不同的从站,但不同端口下不受影响。如下图所示:

从上图中我们可以发现,我们的目的就是让协议栈支持,多主站和多从站,并且在不同主站下,从站的地址重复不受影响。接下来我们还需要考虑从站对象。主站对从站的操作无非两类:读从站信息和写从站信息。

对于读从站信息来说,主站需要发送请求命令,等待从站返回响应信息,然后主站解析收到的信息并更新对应的参数值。有两点需要我们考虑,第一返回的响应消息是没有对应的寄存器地址的,所以要想在解析的时候定位寄存器就必须知道发送的命令,为了便于分辨我们将命令存放在从站对象中。第二在解析响应时,如果两条命令的响应类似是没法分辨的,所以我们还需要记住上一条命令是什么。也存储于从站对象中。

而对于写从站操作,无论写的要求来自于哪里,对于协议栈来说肯定是其它的数据处理进程发过来的,所接到要求后我们需要记录是哪一个主站管理的哪一个从站的哪些参数。对于主站我们不需要分辨,因为每个主站都是独立的处理进程,但是对于从站和参数我们就需要分辨。每一个主站可以带的站地址为0到255,但0和255已有定义,所以实际是1到254个。所以我们使用一个256位的变量,每位对应站号来标志其是否有需要写的请求。记录于主站,具体如下:

而每个从站的写参数请求标志则存储于各个从站对象,因为不同的从站可能有很大区别,存储于各个从站更加灵活。

3 、如何实现

我们已经设计了我们的更新,但具体如何实现它呢?我们主要从以下几个方面来实现它。第一,实现主站对象类型和从站对象类型。第二,主站对象的实例化及从站对象的实例化。第三,读从站的主站操作过程。第四,写从站的主站操作过程。接下来我们将一一描述之。

3.1 、定义对象类型

在Modbus RTU协议栈的封装中,我们需要定义主站对象和从站对象,自然也需要定义这两种类型。至于其功能前述已经描述过。

首先我们来定义本地主站的类型,其成员包括:一个uint32_t的写从站标志数组;从站数量字段;从站顺序字段;本主站所管理的从站列表;4个数据更新函数指针。具体定义如下:

1 /* 定义本地RTU主站对象类型 */
 2 typedef struct LocalRTUMasterType{
 3   uint32_t flagWriteSlave[8];   //写一个站控制标志位,最多256个站,与站地址对应。
 4   uint16_t slaveNumber;         //从站列表中从站的数量
 5   uint16_t readOrder;           //当前从站在从站列表中的位置
 6   RTUAccessedSlaveType *pSlave;         //从站列表
 7   UpdateCoilStatusType pUpdateCoilStatus;       //更新线圈量函数
 8   UpdateInputStatusType pUpdateInputStatus;     //更新输入状态量函数
 9   UpdateHoldingRegisterType pUpdateHoldingRegister;     //更新保持寄存器量函数
10   UpdateInputResgisterType pUpdateInputResgister;       //更新输入寄存器量函数
11 }RTULocalMasterType;

关于主站对象类型,在前面的更新设计中已经讲的很清楚了,只有两个需要说明一下。第一,从站列表是用来记录本主站所管理的从站对象。第二,readOrder字段表示为当前访问从站在列表中的位置,而slaveNumber是从站对象的数量,即列表的长度。具体如下图所示:

还需要定义从站对象,此从站对象只是便于主站而用于表示真实的从站。主站的从站列表中就是此对象。具体结构如下:

1 /* 定义被访问RTU从站对象类型 */
 2 typedef struct AccessedRTUSlaveType{
 3   uint8_t stationAddress;       //站地址
 4   uint8_t cmdOrder;             //当前命令在命令列表中的位置
 5   uint16_t commandNumber;       //命令列表中命令的总数
 6   uint8_t (*pReadCommand)[8];   //读命令列表
 7   uint8_t *pLastCommand;        //上一次发送的命令
 8   uint32_t flagPresetCoil;      //预置线圈控制标志位
 9   uint32_t flagPresetReg;       //预置寄存器控制标志位
10 }RTUAccessedSlaveType;

关于从站对象有两个字段需要说一下,就是flagPresetCoil和flagPresetReg字段。这两个字段用来表示对线圈和保持寄存器的写请求。

3.2 、实例化对象

我们定义了主站即从站对象类型,我们在使用时就需要实例化这些对象。一般来说一个硬件端口我们将其实例化为一个主站对象。

RTULocalMasterType hgraMaster;

/ 初始化RTU主站对象 /

InitializeRTUMasterObject(&hgraMaster,2,hgraSlave,NULL,NULL,NULL,NULL);

而一个主站对象会管理1到254个从站对象,所以我们可以将多个从站对象实例组成数组,并将其赋予主站管理。

RTUAccessedSlaveType hgraSlave[]={{1,0,2,slave1ReadCommand,NULL,0x00,0x00},{2,0,2,slave2ReadCommand,NULL,0x00,0x00}};

所以,根据主站和从站实例化的条件,我们需要先实例化从站对象才能完整实例化主站对象。在主站的初始化中,我们这里将4的数据处理函数指针初始化为NULL,有一个默认的处理函数会复制给它,该函数是上一版本的延续,在简单应用时简化操作。从站的上一个发送的命令指针也被赋值为NULL,因为初始时还没有命令发送。

3.3 、读从站操作

读从站操作原理上与以前的版本是一样的。按照一定的顺序给从站发送命令再对收到的消息进行解析。我们对主站及其所管理的从站进行了定义,将发送命令保存于从站对象,将从站列表保存于主站对象,所以我们需要对解析函数进行修改。

1 /*解析收到的服务器相应信息*/
 2 /*uint8_t *recievedMessage,接收到的消息列表*/
 3 /*uint8_t *command,发送的读操作命令,若为NULL则在命令列表中查找*/
 4 void ParsingSlaveRespondMessage(RTULocalMasterType *master,uint8_t *recievedMessage,uint8_t *command)
 5 {
 6   int i=0;
 7   int j=0;
 8   uint16_t startAddress;
 9   uint16_t quantity;
10   uint8_t *cmd=NULL;
11  
12   /*如果不是读操作的反回信息不需要处理*/
13   if(recievedMessage[1]>0x04)
14   {
15     return;
16   }
17  
18   /*判断功能码是否有误*/
19   FunctionCode fuctionCode=(FunctionCode)recievedMessage[1];
20   if (CheckFunctionCode(fuctionCode) != MB_OK)
21   {
22     return;
23   }
24  
25   /*校验接收到的信息是否有错*/
26   uint16_t byteCount=recievedMessage[2];
27   bool chechMessageNoError=CheckRTUMessageIntegrity(recievedMessage,byteCount+5);
28   if(!chechMessageNoError)
29   {
30     return;
31   }
32  
33   if((command==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,command)))
34   {
35     while(islaveNumber)
36     {
37       if(master->pSlave[i].stationAddress==recievedMessage[0])
38       {
39         break;
40       }
41       i++;
42     }
43    
44     if(i>=master->slaveNumber)
45     {
46       return;
47     }
48    
49     if((master->pSlave[i].pLastCommand==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,master->pSlave[i].pLastCommand)))
50     {
51       j=FindCommandForRecievedMessage(recievedMessage,master->pSlave[i].pReadCommand,master->pSlave[i].commandNumber);
52      
53       if(j<0)
54       {
55         return;
56       }
57      
58       cmd=master->pSlave[i].pReadCommand[j];
59     }
60     else
61     {
62       cmd=master->pSlave[i].pLastCommand;
63     }
64   }
65   else
66   {
67     cmd=command;
68   }
69  
70   startAddress=(uint16_t)cmd[2];
71   startAddress=(startAddress<<8)+(uint16_t)cmd[3];
72   quantity=(uint16_t)cmd[4];
73   quantity=(quantity<<8)+(uint16_t)cmd[5];
74  
75   if((fuctionCode>=ReadCoilStatus)&&(fuctionCode<=ReadInputRegister))
76   {
77     HandleSlaveRespond[fuctionCode-1](master,recievedMessage,startAddress,quantity);
78   }
79 }

解析函数的主要部分是在检查接收到的消息是否是合法的Modbus RTU消息。检查没问题则调用协议站解析。而最后调用的数据处理函数则是我们需要在具体应用中编写。在前面主站初始化时,回调函数我们初始化为NULL,实际在协议栈中有弱化的函数定义,需要针对具体的寄存器和变量地址实现操作。

3.4 、写从站操作

写从站操作则是在其它进程请求后,我们标识需要写的对象再统一处理。对具体哪个从站的写标识存于主站实例。而该从站的哪些变量需要写则记录在从站实例中。

所以在进程检测到需要写一个从站时则置位对应的位,即改变flagWriteSlave中的对应位。而需要写该站的哪些变量则标记flagPresetCoil和flagPresetReg的对应位。修改这些标识都在其它请求更改的进程中实现,而具体的写操作则在本主站进程中,检测到标志位的变化统一执行。

这部分不修改协议栈的代码,因为各站及各变量都至于具体对象相关联,所以在具体的应用中修改。

4 、回归验证

为了验证我们前面的更新设计是符合要求的,我们设计一个难度较高的实验系统。这一实验系统包括Modbus网关,上位Modbus主站以及下位的Modbus从站。我们所要实现的是Modbus网关部分,其具体结构图设计如下:

从上图我们知道,该Modbus网关需要实现一个Modbus从站用于和上位的通讯;需要实现两个Modbus主站用于和下位的通讯。

在这个实验中,读操作没有什么需要说的,只需要发送命令,解析返回消息即可。所以我们重点描述一下写操作,为了方便操作,在需要写的连续段,我们只要找到第一个请求写的位置后,就将后续连续可写数据一次性写入。修改写标志位的代码如下:

1 /* 写从站寄存器控制 */
  2 static void WriteSlaveRegisterControll(uint16_t startAddress,uint16_t endAddress)
  3 {
  4   if((12<=startAddress)&&(startAddress<=71)&&(12<=endAddress)&&(endAddress<=71))
  5   {
  6     ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[0].stationAddress,true);
  7    
  8     if((startAddress<=12)&&(13<=endAddress))
  9     {
 10       hgraMaster.pSlave[0].flagPresetReg|=0x01;
 11     }
 12     if((startAddress<=14)&&(15<=endAddress))
 13     {
 14       hgraMaster.pSlave[0].flagPresetReg|=0x02;
 15     }
 16     if((startAddress<=16)&&(17<=endAddress))
 17     {
 18       hgraMaster.pSlave[0].flagPresetReg|=0x04;
 19     }
 20     if((startAddress<=18)&&(19<=endAddress))
 21     {
 22       hgraMaster.pSlave[0].flagPresetReg|=0x08;
 23     }
 24     if((startAddress<=20)&&(21<=endAddress))
 25     {
 26       hgraMaster.pSlave[0].flagPresetReg|=0x10;
 27     }
 28     if((startAddress<=22)&&(23<=endAddress))
 29     {
 30       hgraMaster.pSlave[0].flagPresetReg|=0x20;
 31     }
 32     if((startAddress<=24)&&(25<=endAddress))
 33     {
 34       hgraMaster.pSlave[0].flagPresetReg|=0x40;
 35     }
 36     if((startAddress<=26)&&(27<=endAddress))
 37     {
 38       hgraMaster.pSlave[0].flagPresetReg|=0x80;
 39     }
 40    
 41     if((startAddress<=32)&&(32<=endAddress))
 42     {
 43       hgraMaster.pSlave[0].flagPresetReg|=0x100;
 44     }
 45     if((startAddress<=33)&&(33<=endAddress))
 46     {
 47       hgraMaster.pSlave[0].flagPresetReg|=0x200;
 48     }
 49     if((startAddress<=34)&&(34<=endAddress))
 50     {
 51       hgraMaster.pSlave[0].flagPresetReg|=0x400;
 52     }
 53     if((startAddress<=35)&&(35<=endAddress))
 54     {
 55       hgraMaster.pSlave[0].flagPresetReg|=0x800;
 56     }
 57     if((startAddress<=36)&&(36<=endAddress))
 58     {
 59       hgraMaster.pSlave[0].flagPresetReg|=0x1000;
 60     }
 61     if((startAddress<=37)&&(37<=endAddress))
 62     {
 63       hgraMaster.pSlave[0].flagPresetReg|=0x2000;
 64     }
 65     if((startAddress<=38)&&(38<=endAddress))
 66     {
 67       hgraMaster.pSlave[0].flagPresetReg|=0x4000;
 68     }
 69     if((startAddress<=39)&&(39<=endAddress))
 70     {
 71       hgraMaster.pSlave[0].flagPresetReg|=0x8000;
 72     }
 73     if((startAddress<=40)&&(40<=endAddress))
 74     {
 75       hgraMaster.pSlave[0].flagPresetReg|=0x10000;
 76     }
 77     if((startAddress<=41)&&(41<=endAddress))
 78     {
 79       hgraMaster.pSlave[0].flagPresetReg|=0x20000;
 80     }
 81     if((startAddress<=42)&&(42<=endAddress))
 82     {
 83       hgraMaster.pSlave[0].flagPresetReg|=0x40000;
 84     }
 85     if((startAddress<=43)&&(43<=endAddress))
 86     {
 87       hgraMaster.pSlave[0].flagPresetReg|=0x80000;
 88     }
 89     if((startAddress<=44)&&(44<=endAddress))
 90     {
 91       hgraMaster.pSlave[0].flagPresetReg|=0x100000;
 92     }
 93     if((startAddress<=45)&&(45<=endAddress))
 94     {
 95       hgraMaster.pSlave[0].flagPresetReg|=0x200000;
 96     }
 97     if((startAddress<=46)&&(46<=endAddress))
 98     {
 99       hgraMaster.pSlave[0].flagPresetReg|=0x400000;
100     }
101     if((startAddress<=47)&&(47<=endAddress))
102     {
103       hgraMaster.pSlave[0].flagPresetReg|=0x800000;
104     }
105    
106     if((startAddress<=52)&&(55<=endAddress))
107     {
108       hgraMaster.pSlave[0].flagPresetReg|=0x1000000;
109     }
110     if((startAddress<=56)&&(59<=endAddress))
111     {
112       hgraMaster.pSlave[0].flagPresetReg|=0x2000000;
113     }
114     if((startAddress<=60)&&(63<=endAddress))
115     {
116       hgraMaster.pSlave[0].flagPresetReg|=0x4000000;
117     }
118     if((startAddress<=64)&&(67<=endAddress))
119     {
120       hgraMaster.pSlave[0].flagPresetReg|=0x8000000;
121     }
122   }
123  
124   if((72<=startAddress)&&(startAddress<=131)&&(72<=endAddress)&&(endAddress<=131))
125   {
126     ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[1].stationAddress,true);
127    
128     if((startAddress<=72)&&(73<=endAddress))
129     {
130       hgraMaster.pSlave[1].flagPresetReg|=0x01;
131     }
132     if((startAddress<=74)&&(75<=endAddress))
133     {
134       hgraMaster.pSlave[1].flagPresetReg|=0x02;
135     }
136     if((startAddress<=76)&&(77<=endAddress))
137     {
138       hgraMaster.pSlave[1].flagPresetReg|=0x04;
139     }
140     if((startAddress<=78)&&(79<=endAddress))
141     {
142       hgraMaster.pSlave[1].flagPresetReg|=0x08;
143     }
144     if((startAddress<=80)&&(81<=endAddress))
145     {
146       hgraMaster.pSlave[1].flagPresetReg|=0x10;
147     }
148     if((startAddress<=82)&&(83<=endAddress))
149     {
150       hgraMaster.pSlave[1].flagPresetReg|=0x20;
151     }
152     if((startAddress<=84)&&(85<=endAddress))
153     {
154       hgraMaster.pSlave[1].flagPresetReg|=0x40;
155     }
156     if((startAddress<=86)&&(87<=endAddress))
157     {
158       hgraMaster.pSlave[1].flagPresetReg|=0x80;
159     }
160    
161     if((startAddress<=92)&&(92<=endAddress))
162     {
163       hgraMaster.pSlave[1].flagPresetReg|=0x100;
164     }
165     if((startAddress<=93)&&(93<=endAddress))
166     {
167       hgraMaster.pSlave[1].flagPresetReg|=0x200;
168     }
169     if((startAddress<=94)&&(94<=endAddress))
170     {
171       hgraMaster.pSlave[1].flagPresetReg|=0x400;
172     }
173     if((startAddress<=95)&&(95<=endAddress))
174     {
175       hgraMaster.pSlave[1].flagPresetReg|=0x800;
176     }
177     if((startAddress<=96)&&(96<=endAddress))
178     {
179       hgraMaster.pSlave[1].flagPresetReg|=0x1000;
180     }
181     if((startAddress<=97)&&(97<=endAddress))
182     {
183       hgraMaster.pSlave[1].flagPresetReg|=0x2000;
184     }
185     if((startAddress<=98)&&(98<=endAddress))
186     {
187       hgraMaster.pSlave[1].flagPresetReg|=0x4000;
188     }
189     if((startAddress<=99)&&(99<=endAddress))
190     {
191       hgraMaster.pSlave[1].flagPresetReg|=0x8000;
192     }
193     if((startAddress<=100)&&(100<=endAddress))
194     {
195       hgraMaster.pSlave[1].flagPresetReg|=0x10000;
196     }
197     if((startAddress<=101)&&(101<=endAddress))
198     {
199       hgraMaster.pSlave[1].flagPresetReg|=0x20000;
200     }
201     if((startAddress<=102)&&(102<=endAddress))
202     {
203       hgraMaster.pSlave[1].flagPresetReg|=0x40000;
204     }
205     if((startAddress<=103)&&(103<=endAddress))
206     {
207       hgraMaster.pSlave[1].flagPresetReg|=0x80000;
208     }
209     if((startAddress<=104)&&(104<=endAddress))
210     {
211       hgraMaster.pSlave[1].flagPresetReg|=0x100000;
212     }
213     if((startAddress<=105)&&(105<=endAddress))
214     {
215       hgraMaster.pSlave[1].flagPresetReg|=0x200000;
216     }
217     if((startAddress<=106)&&(106<=endAddress))
218     {
219       hgraMaster.pSlave[1].flagPresetReg|=0x400000;
220     }
221     if((startAddress<=107)&&(107<=endAddress))
222     {
223       hgraMaster.pSlave[1].flagPresetReg|=0x800000;
224     }
225    
226     if((startAddress<=112)&&(115<=endAddress))
227     {
228       hgraMaster.pSlave[1].flagPresetReg|=0x1000000;
229     }
230     if((startAddress<=116)&&(119<=endAddress))
231     {
232       hgraMaster.pSlave[1].flagPresetReg|=0x2000000;
233     }
234     if((startAddress<=120)&&(123<=endAddress))
235     {
236       hgraMaster.pSlave[1].flagPresetReg|=0x4000000;
237     }
238     if((startAddress<=124)&&(127<=endAddress))
239     {
240       hgraMaster.pSlave[1].flagPresetReg|=0x8000000;
241     }
242   }
243  
244   if((132<=startAddress)&&(startAddress<=191)&&(131<=endAddress)&&(endAddress<=191))
245   {
246     ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[0].stationAddress,true);
247   }
248  
249   if((192<=startAddress)&&(startAddress<=251)&&(192<=endAddress)&&(endAddress<=251))
250   {
251     ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[1].stationAddress,true);
252   }
253 }

然后在主站对象的进程中检测标志位,根据标志位的状态来实现操作,具体的操作代码很简单,且不具普遍性,在此不贴出。

5 、几点注意

虽然我们对主站对象和从站对象进行了封装,但我们在使用时人需要注意一些问题。

(1)、4个回调函数的定义,这4个回调函数用于处理从粘返回的信息,对应Modbus定义的四种数据,需要根据主站对象管理的从站情况来实现。

(2)、对于写操作标识符,一般都是在请求进程置位,在主站对象所在的进程检测并操作,然后复位。

告之: 源代码可上Github下载https://github.com/foxclever/Modbus

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • MODBUS
    +关注

    关注

    28

    文章

    1801

    浏览量

    76958
  • ASCII
    +关注

    关注

    5

    文章

    172

    浏览量

    35095
  • RTU
    RTU
    +关注

    关注

    0

    文章

    412

    浏览量

    28672
  • 协议栈
    +关注

    关注

    2

    文章

    141

    浏览量

    33628
收藏 人收藏

    评论

    相关推荐

    开关量转485、485转开关量

    采用Modbus RTU,实现最快捷工业传输. RS485接口端有效实现光电隔离和电源隔离威廉希尔官方网站 ,的效抑制闪电,雷击,ESD和共地干扰.提供五年质保服务。特点:→8路数字量输入→8路数字
    发表于 06-13 16:17

    Modbus TCP转Modbus RTU实现

    使用ZLSN2040、NETCOM2040实现Modbus TCP到Modbus RTU的转化。1.Modbus TCP与
    发表于 08-10 10:04

    中断不能调printf的原因

    [导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题...
    发表于 01-27 08:10

    中断不能调printf的原因

    关注、星标嵌入式客栈,精彩及时送达[导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友问在一个串口接收中...
    发表于 02-07 06:53

    Modbus RTU实现工控机与智能仪表通信

    能力等特点,非常适合组成工业级的多机通信系统。Modbus RIU通信协议是目前国际智能化仪表普遍采用的主流通信协议之一。在各种工业仪器仪表大量使用的今天,Modbus RIU通信协议和RS - 485总线得到了广泛的应用。本文主要从应用角度介绍在工业
    发表于 01-15 11:24 2次下载

    扒一扒中断为什么不能调printf

    [导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题...
    发表于 12-03 14:51 0次下载
    扒一扒中断为什么不能调printf

    扒一扒中断为什么不能调printf

    [导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题...
    发表于 12-04 11:36 8次下载
    扒一扒中断为什么不能调printf

    扒一扒中断为什么不能调printf

    [导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题...
    发表于 12-04 12:21 6次下载
    扒一扒中断为什么不能调printf

    扒一扒中断为什么不能调printf?

    前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串口接收中断里打印遇到了问题,今天聊下这个话题。扒一扒printf对于单片...
    发表于 12-04 12:21 0次下载
    扒一扒中断为什么不能调printf?

    扒一扒中断为什么不能调printf

    [导读] 大家好,我是逸珺。前面说会写一下Modbus-RTU实现,写了1000多字了,有兴趣的稍等一下哈。前面在一个群里看到一个朋友在一个串...
    发表于 01-25 17:52 0次下载
    扒一扒中断为什么不能调printf

    西门子SMARTP通过MODBUS RTU实现一主多从的步骤

    通过SMART PLC进行编程过程中,通常会遇到需要将多个RTU从站进行传输,而作为SMARTPLC的主站接收多个从站传输过来的数据,并同步显示。
    的头像 发表于 07-08 14:18 1923次阅读
    西门子SMARTP通过<b class='flag-5'>MODBUS</b> <b class='flag-5'>RTU</b><b class='flag-5'>实现</b>一主多从的步骤

    Profibus DP主站转Modbus RTU协议网关(Modbus RTU转Profibus DP)

    Profibus DP主站转Modbus RTU如何实现有效连接与通信,这一问题常常让许多人感到困惑不已。现在,就来为大家专门解答这个疑问。远创智控YC-DPM-RTU设备有着极为出色
    的头像 发表于 08-25 14:43 306次阅读
    Profibus DP主站转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>协议网关(<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>转Profibus DP)

    EtherCAT从站转Modbus RTU总线协议转换网关配置详情

      EtherCAT转Modbus RTU如何实现有效连接与通信,这一问题常常让许多人感到困惑不已。现在,就来为大家专门解答这个疑问。远创智控YC-ECT-RTU型设备有着极为出色的表
    的头像 发表于 08-27 16:02 277次阅读
    EtherCAT从站转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>总线协议转换网关配置详情

    Profibus DP从站转Modbus RTU主站总线协议转换网关配置详情

    Profibus DP转Modbus RTU如何实现有效连接与通信,这一问题常常让许多人感到困惑不已。现在,就来为大家专门解答这个疑问。远创智控YC-DPM-TCP型设备有着极为出色的表现,能够
    的头像 发表于 08-27 16:20 291次阅读
    Profibus DP从站转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>主站总线协议转换网关配置详情

    EtherCAT转Modbus-RTU协议网关(Modbus-RTU转EtherCAT)

    EtherCAT转Modbus-RTU实现网络协议互通是众人关注焦点,远创智控YC-ECT-RTU能够很轻松解决这个问题。在这里作者将从该设备的主要功能、威廉希尔官方网站 参数、性能优势、配置方法等几个方面详细
    的头像 发表于 09-04 09:56 239次阅读
    EtherCAT转<b class='flag-5'>Modbus-RTU</b>协议网关(<b class='flag-5'>Modbus-RTU</b>转EtherCAT)