Modbus协议栈实现Modbus RTU多主站支持

前面我们已经详细讲解过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个数据更新函数指针。具体定义如下:

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

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

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

/* 定义被访问RTU从站对象类型 */
typedef struct AccessedRTUSlaveType{uint8_t stationAddress;       //站地址uint8_t cmdOrder;             //当前命令在命令列表中的位置uint16_t commandNumber;       //命令列表中命令的总数uint8_t (*pReadCommand)[8];   //读命令列表uint8_t *pLastCommand;        //上一次发送的命令uint32_t flagPresetCoil;      //预置线圈控制标志位uint32_t flagPresetReg;       //预置寄存器控制标志位
}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、读从站操作

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

/*解析收到的服务器相应信息*/
/*uint8_t *recievedMessage,接收到的消息列表*/
/*uint8_t *command,发送的读操作命令,若为NULL则在命令列表中查找*/
void ParsingSlaveRespondMessage(RTULocalMasterType *master,uint8_t *recievedMessage,uint8_t *command)
{int i=0;int j=0;uint16_t startAddress;uint16_t quantity;uint8_t *cmd=NULL;/*如果不是读操作的反回信息不需要处理*/if(recievedMessage[1]>0x04){return;}/*判断功能码是否有误*/FunctionCode fuctionCode=(FunctionCode)recievedMessage[1];if (CheckFunctionCode(fuctionCode) != MB_OK){return;}/*校验接收到的信息是否有错*/uint16_t byteCount=recievedMessage[2];bool chechMessageNoError=CheckRTUMessageIntegrity(recievedMessage,byteCount+5);if(!chechMessageNoError){return;}if((command==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,command))){while(i<master->slaveNumber){if(master->pSlave[i].stationAddress==recievedMessage[0]){break;}i++;}if(i>=master->slaveNumber){return;}if((master->pSlave[i].pLastCommand==NULL)||(!CheckMessageAgreeWithCommand(recievedMessage,master->pSlave[i].pLastCommand))){j=FindCommandForRecievedMessage(recievedMessage,master->pSlave[i].pReadCommand,master->pSlave[i].commandNumber);if(j<0){return;}cmd=master->pSlave[i].pReadCommand[j];}else{cmd=master->pSlave[i].pLastCommand;}}else{cmd=command;}startAddress=(uint16_t)cmd[2];startAddress=(startAddress<<8)+(uint16_t)cmd[3];quantity=(uint16_t)cmd[4];quantity=(quantity<<8)+(uint16_t)cmd[5];if((fuctionCode>=ReadCoilStatus)&&(fuctionCode<=ReadInputRegister)){HandleSlaveRespond[fuctionCode-1](master,recievedMessage,startAddress,quantity);}
}

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

3.4、写从站操作

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

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

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

4、回归验证

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

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

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

/* 写从站寄存器控制 */
static void WriteSlaveRegisterControll(uint16_t startAddress,uint16_t endAddress)
{if((12<=startAddress)&&(startAddress<=71)&&(12<=endAddress)&&(endAddress<=71)){ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[0].stationAddress,true);if((startAddress<=12)&&(13<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x01;}if((startAddress<=14)&&(15<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x02;}if((startAddress<=16)&&(17<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x04;}if((startAddress<=18)&&(19<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x08;}if((startAddress<=20)&&(21<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x10;}if((startAddress<=22)&&(23<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x20;}if((startAddress<=24)&&(25<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x40;}if((startAddress<=26)&&(27<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x80;}if((startAddress<=32)&&(32<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x100;}if((startAddress<=33)&&(33<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x200;}if((startAddress<=34)&&(34<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x400;}if((startAddress<=35)&&(35<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x800;}if((startAddress<=36)&&(36<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x1000;}if((startAddress<=37)&&(37<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x2000;}if((startAddress<=38)&&(38<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x4000;}if((startAddress<=39)&&(39<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x8000;}if((startAddress<=40)&&(40<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x10000;}if((startAddress<=41)&&(41<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x20000;}if((startAddress<=42)&&(42<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x40000;}if((startAddress<=43)&&(43<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x80000;}if((startAddress<=44)&&(44<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x100000;}if((startAddress<=45)&&(45<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x200000;}if((startAddress<=46)&&(46<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x400000;}if((startAddress<=47)&&(47<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x800000;}if((startAddress<=52)&&(55<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x1000000;}if((startAddress<=56)&&(59<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x2000000;}if((startAddress<=60)&&(63<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x4000000;}if((startAddress<=64)&&(67<=endAddress)){hgraMaster.pSlave[0].flagPresetReg|=0x8000000;}}if((72<=startAddress)&&(startAddress<=131)&&(72<=endAddress)&&(endAddress<=131)){ModifyWriteRTUSlaveEnableFlag(&hgraMaster,hgraMaster.pSlave[1].stationAddress,true);if((startAddress<=72)&&(73<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x01;}if((startAddress<=74)&&(75<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x02;}if((startAddress<=76)&&(77<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x04;}if((startAddress<=78)&&(79<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x08;}if((startAddress<=80)&&(81<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x10;}if((startAddress<=82)&&(83<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x20;}if((startAddress<=84)&&(85<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x40;}if((startAddress<=86)&&(87<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x80;}if((startAddress<=92)&&(92<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x100;}if((startAddress<=93)&&(93<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x200;}if((startAddress<=94)&&(94<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x400;}if((startAddress<=95)&&(95<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x800;}if((startAddress<=96)&&(96<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x1000;}if((startAddress<=97)&&(97<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x2000;}if((startAddress<=98)&&(98<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x4000;}if((startAddress<=99)&&(99<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x8000;}if((startAddress<=100)&&(100<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x10000;}if((startAddress<=101)&&(101<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x20000;}if((startAddress<=102)&&(102<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x40000;}if((startAddress<=103)&&(103<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x80000;}if((startAddress<=104)&&(104<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x100000;}if((startAddress<=105)&&(105<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x200000;}if((startAddress<=106)&&(106<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x400000;}if((startAddress<=107)&&(107<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x800000;}if((startAddress<=112)&&(115<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x1000000;}if((startAddress<=116)&&(119<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x2000000;}if((startAddress<=120)&&(123<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x4000000;}if((startAddress<=124)&&(127<=endAddress)){hgraMaster.pSlave[1].flagPresetReg|=0x8000000;}}if((132<=startAddress)&&(startAddress<=191)&&(131<=endAddress)&&(endAddress<=191)){ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[0].stationAddress,true);}if((192<=startAddress)&&(startAddress<=251)&&(192<=endAddress)&&(endAddress<=251)){ModifyWriteRTUSlaveEnableFlag(&hgpjMaster,hgpjMaster.pSlave[1].stationAddress,true);}
}

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

5、几点注意

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

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

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

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

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32基于AD5663的UV灯电压控制

在开发臭氧发生器的时&#xff0c;我们使用UV灯来实现臭氧的产生。而UV灯的强度决定了臭氧产生的浓度&#xff0c;UV灯的光强则与其控制电压密切相关。所以我们要控制产生的臭氧的浓度就需要调节其控制电压。我们选择了AD5663这一模拟量输出模块来实现这一点。 1、AD5663简介 …

实现Modbus ASCII多主站应用

前面我们已经分析了Modbus RTU的更新设计和具体实现&#xff08;如果不清楚可查看前一篇文章&#xff09;。其实Modbus ASCII与Modbus RTU都是基于串行链路实现的&#xff0c;所以有很多的共同点&#xff0c;基于此&#xff0c;这篇文章我们只讨论与Modbus RTU所不同的部分。 …

STM32一种基于NTC的控温电路及软件实现

NTC&#xff08;Negative Temperature Coefficient&#xff09;是一种随温度上升时&#xff0c;电阻值呈指数关系减小的热敏电阻。应用广泛&#xff0c;最近我们就采用了NTC来控制加热并测温&#xff0c;并达到了预期的效果。 1、硬件设计 我们使用三极管作为加热元件&#x…

STM32利用光敏二极管实现光度测量

最近我们在开发臭氧发生器时&#xff0c;需要监测生成的臭氧的浓度&#xff0c;于是想到使用光度计来测量。因为不同浓度的臭氧对管的吸收作用是不相同的&#xff0c;于是检测光照强度的变化就可以得到相应的浓度数据。 1、硬件设计 此次光照度检测我们选用了S1336-5BQ光电点二…

STM32的ADC通道间干扰的问题

最近我们在开发一个项目时&#xff0c;用到了MCU自带的ADC&#xff0c;在调试过程中发现通道之间村在相互干扰的问题。以前其实也用过好几次&#xff0c;但要求都不高所以没有太关注&#xff0c;此次因为物理量的量程较大&#xff0c;所以看到了变化。 首先来说明一下此次的软…

实现Modbus TCP多网段客户端应用

对于Modbus TCP来说与Modbus RTU和Modbus ASCII有比较大的区别&#xff0c;因为它是运行于以太网链路之上&#xff0c;是运行于TCP/IP协议之上的一种应用层协议。在协议栈的前两个版本中&#xff0c;Modbus TCP作为客户端时也存在一些局限性。我们将对这些不足作一定更新。 1、…

改进初学者的PID-介绍

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-采样时间

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-微分冲击

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

LwIP应用开发笔记之一:LwIP无操作系统基本移植

现在&#xff0c;TCP/IP协议的应用无处不在。随着物联网的火爆&#xff0c;嵌入式领域使用TCP/IP协议进行通讯也越来越广泛。在我们的相关产品中&#xff0c;也都有应用&#xff0c;所以我们结合应用实际对相关应用作相应的总结。 1、技术准备 我们采用的开发平台是STM32F407…

改进初学者的PID-修改整定参数

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-积分饱和

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-手自动切换

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-初始化

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

如何优化代码和RAM大小

如果供应商为我自己的项目提供了一个起点&#xff0c;那就太好了。工作blinky始终是一个伟大的首发。方便总是有代价&#xff0c;而且“blinky”就是夸大“切换GPIO引脚”的代码大小。对于具有少量RAM和FLASH的设备&#xff0c;这可能会引起关注&#xff1a;如果blinky占用那么…

改进初学者的PID-正反作用

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-测量的比例介绍

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

改进初学者的PID-测量的比例编码

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

PID:我应该何时计算积分项?

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…

Arduino PID自整定库

最近看到了Brett Beauregard发表的有关PID的系列文章&#xff0c;感觉对于理解PID算法很有帮助&#xff0c;于是将系列文章翻译过来&#xff01;在自我提高的过程中&#xff0c;也希望对同道中人有所帮助。作者Brett Beauregard的原文网址&#xff1a;http&#xff1a;//brettb…