tag 标签: freemodbus

相关博文
  • 热度 13
    2021-7-9 21:10
    1168 次阅读|
    0 个评论
    FreeModbus从站设计(12)-Modbus的通信参数存在flash中,如果改乱了,该怎么办 关键词:FreeModbus 复位默认参数 HAL库 flash STM32 在上一篇文章中,介绍了如何将Modbus的通信参数存储在单片机STM32F103C8T6的flash中,这样可以简化硬件电路设计,功能扩展也更加灵活。但孔丙火(微信公众号:孔丙火)认为,这种方法也有副作用,因为是用通信的方法修改参数,如果用户把上次修改的参数忘记了,岂不是无法建立串口连接了?要么一个一个参数地试,费时费力,要么重新刷单片机的程序,但这需要开发工程师的参与。公众号之前有一篇文章,是讲PLC的上电停止功能的,这里可以做一个借鉴,来解决这个问题。大体的意思,就是为用户保留一个数据命令,当单片机上电的时候,收到这个命令,就采用默认的通信参数运行,这时用户是可以通过串口连接单片机的,然后再把通信参数改成自己需要的就可以了。 下面来讲一下具体方法。 (1)设置默认通信参数,孔丙火(微信公众号:孔丙火)这里称之为“复位默认参数”,就是说单片机复位后,每次都是按照这个参数来运行Modbus,经过一定的时间后,再切换到用户设定的参数。 如图1所示,在程序的初始化阶段,先设置默认通信参数,这里设为:从站地址为1,波特率19200bps,偶校验,停止位1位。 (2)用定时器做一个定时,定时500ms(这个根据自己的实际情况来定),在这个时间内如果收到按默认参数运行的数据指令,则不切换通信参数,否则切换到用户设定的Modbus参数。 如图2所示,在回调函数eMBRegHoldingCB()中,这个函数在之前的章节中讲过的,当收到写保持寄存器命令后,先对相应的数组进行处理,然后判断ucUsrInitBaud这个变量,这个变量的初始化值为0定时500ms后再定时器的中断中将其置位1,在500ms以内,如果收到按默认参数运行的数据指令,则响应,过了500ms,则不响应这个指令。 图1 图2 如果从站地址为1,从地址0开始写寄存器的数量为2,分别为0xff55,和0xaaff,则一直按默认参数运行Modbus。也就是说,如果收到主站发来的数据为:01 10 00 00 00 02 04 FF 55 AA FF ED 4B,则一直按默认参数运行Modbus。用变量ucUsrTurnonStop来标记。 (3)切换用户设定的Modbus参数。如图3所示,在定时器溢出中断回调函数中,在第一次500ms定时时间到时,对ucUsrComReset变量赋值,为0xff表示一直按默认参数运行Modbus,为0x55表示切换用户设定的Modbus参数。 图3 如图4所示,在程序主循环中,如果ucUsrComReset==0x55,则先停止停止协议栈,然后重新进行初始化,并将ucUsrComReset置为0xff,保证只进行一次重新初始化。 图4 孔丙火(微信公众号:孔丙火)这里展示的是我的实现方法,主要是讲思路,朋友们也可以有自己的实现逻辑和方法,而且我这个方法并不是最好的。 至此,就实现了上电500ms内按照“复位默认参数”运行Modbus,然后根据主站传输来的数据,判断下一步怎么运行。如果需要按“复位默认参数”运行,则需要在500ms内,向从站发送:01 10 00 00 00 02 04 FF 55 AA FF ED 4B。通常的做法是:在单片机上电前,不停的向其发送这个数据,然后再给单片机上电。 如果用户忘记了之前修改的Modbus通信参数,则可以在“复位默认参数”状态,按照上一节讲的在线修改通信参数的方法,改成自己需要的参数,然后再把单片机重启就可以正常的通信了。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 18
    2021-7-9 15:21
    1980 次阅读|
    0 个评论
    FreeModbus从站设计(11)-把Modbus的通信参数存在单片机的flash中 关键词:FreeModbus CubeMX HAL库 flash 通信参数 作为从站,Modbus-RTU通信的参数主要包括从站地址和串口参数,串口参数又包括波特率、校验位、停止位等,把这些参数存储在单片机的flash里,孔丙火(微信公众号:孔丙火)认为,可以简化电路设计,应用更加灵活。通用串口参数中数据位,在Modbus-RTU通信中是不需要设置的,因为Modbus协议规范规定,Modbus-RTU的数据位必须为8位。 1. 通信参数在 flash 中的存储位置 STM32F103C8T6的flash存储区是没有区分程序区和用户数据区的,将通信参数存在flash中,最大的一个原则是不能影响程序代码存储区,否则会有意想不到的后果。具体存在flash中的哪个位置,要根据实际情况来,一般来说,程序代码都不会把flash占满,一般存在最后一页。以此系列文章的示例程序为例,通过keil的编译信息,可以看到程序占用的flash空间只有十几kB,如图1所示,而STM32F103C8T6的flash空间是64kB,共64页,因此擦除、写数据到最后一页不会影响程序代码。 图1 STM32F103C8T6属于中等容量的STM32F103,其flash组织结构如图2所示。中等容量的STM32F103有64kB和128kB两种,STM32F103C8T6是64kB的,因此,只有64页。Flash的操作必须要先擦除,再写入,并且是按页擦除的。最后一页的地址为:0x0800FC00。 图2 2. 基于 HAL 库写 flash 的方法 图3 如图3所示,孔丙火(微信公众号:孔丙火)在modbus_app.c文件中写了一个写flash的函数。总体流程就是先擦除再写入,直接调用HAL库函数就可以了。 3. 通信参数修改的基本流程 基本流程:从站收到修改通信参数的命令→将参数存储到中间数组→写flash→重新初始化串口和协议栈参数。 这里的示例程序,采用写多个保持寄存器(16功能码)传输修改通信参数命令,设定为:从地址15(协议地址格式)开始,写5个寄存器,首个寄存器写入的输入必须为0xFFAA,后面四个寄存器分别表示:从站地址、波特率、校验位、停止位,程序代码如图4所示。 图4 这段代码写在eMBRegHoldingCB()函数中,这个函数在之前的章节有阐述。收到的通信参数存储在了usUsrComFlashData[]中,这是一个全局变量。并且将ucUsrComconfig置1,用于在函数外部写flash的标识。 图5 如图5所示,调用vUsrWriteFlash()函数写flash,此段代码写在主循环while(1)中。 重新初始化串口和协议栈参数,有两种方法,一种是在修改通信参数后,让用户重启,程序在main()函数的初始化阶段完成串口和协议栈参数的初始化,还有一种是在收到修改通信参数的命令后,在线重新初始化串口和协议栈。孔丙火(微信公众号:孔丙火)认为,在工业控制领域,前一种方法更好,因为工控领域最重要的是安全,如果从站正在接收控制命令,这时对串口和协议栈重新初始化,是有危险的,而且修改通信参数的事件,并非高频率。 总结 :介绍了STM32F103C8T6的flash的组织结构,进一步分析了通信参数在flash中的存储位置,阐述了基于HAL库写flash的方法和通信参数修改的基本流程。代码经过实践,可以实现将通信参数存在flash中,并可以通过Modbus命令修改通信参数。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 22
    2021-6-19 22:31
    4068 次阅读|
    0 个评论
    单片机程序中,Modbus功能码的回调函数如何编写--FreeModbus从站设计(10)
    FreeModbus 从站设计( 10 ) -Modbus 功能码的回调函数如何编写 关键词: FreeModbus CubeMX HAL 库 串口 功能码 此系列的前面几篇文章,主要是阐述了用 HAL 库生成 keil 工程、如何将 FreeModbus 的代码加入 keil 工程、协议栈的初始化、串口和定时器接口函数的修改等内容,并整理了 FreeModbus 协议栈调与 HAL 库函数的调用关系,通过以上这些工作, Modbus 的通路(串口和定时器)已经通了,下面就是数据的处理部分了。数据处理部分主要是对各个功能码的回调函数的编写。 以常用的读写保持寄存器的功能码( 03 、 06 、 16 )为例,阐述回调函数的编写方法。 1. 回调函数的位置 第一个是回调函数被调用的地方,也就是回调函数在 FreeModbus 协议栈的哪些地方被使用。 保持寄存器的回调函数为: eMBRegHoldingCB() 。被调用的位置如图 1- 图 4 所示。从几张图片可以看出,调用的位置都在 mbfuncholding.c 文件中,首先说明,在移植 FreeModbus 协议栈的时候, mbfuncholding.c 文件中的代码是不需要修改的,孔丙火(微信公众号:孔丙火)这里只是为了更清晰地展示函数的调用关系,做一个简单的阐述。调用 eMBRegHoldingCB() 的地方主要是写单个保持寄存器(对应的功能码 06 )、写多个保持寄存器(对应的功能码 16 )、读保持寄存器(对应的功能码 03 )、读写多个保持寄存器(对应的功能码 23 ),读写多个保持寄存器的地方调用了两次(读和写),在图 4 中只截取了一个的图片,因此,共有 5 处调用了 eMBRegHoldingCB() 。 图1 图2 图3 图4 这里顺便说一句, FreeModbus 协议栈是严格按照 Modbus 协议标准来做的,在 Modbus 协议规范中,对于保持寄存器的定义见图 5 ,跟读、写寄存器相关的几个功能码,在 FreeModbus 协议栈里面都有定义,这也是使用标准协议栈的好处,相对于自己编写的协议栈,功能全面,不会缺少某些功能。 图5 第二个是回调函数定义的地方。从图 6 可以看出, eMBRegHoldingCB() 函数是字啊 mb.h 中声明的。至于在何处定义(编写)这个函数?只要是在包含了 mb.h 的任何一个 .c 文件中都可以。 图6 2. 回调函数编写方法 在孔丙火(微信公众号:孔丙火)的这个例子中,新建了一个 modbus_app.c 文件, eMBRegHoldingCB() 的代码就编写在 modbus_app.c 中。如图 7 所示,是 eMBRegHoldingCB() 函数的总体结构,首先判断需要读或写的寄存器地址是否在定义的范围内,不在范围内的话,返回 MB_ENOREG ,这样协议栈就会回复相应的错误代码。 图7 如图 8 所示,在读或写的数据在范围内时,对数据进行操作。在程序内部,保持寄存器的数据时存储在数组 usRegHoldingBuf[] 中的,大小根据实际情况自行定义。 图8 图 8 所展示的是一个最基本的读写操作。在实际应用中,也许需要根据保持寄存器中的值做一些操作,后续的操作代码也可以写在这个函数中,这样可以保证第一时间执行。如图 9 所示,就是一个在收到相应的指令后,进入上电停止状态和修改通信参数的例子,后续会有专门一个章节,对此进行阐述。 图9 按照本文的思路,就可以编写自己的功能码回调函数了。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、 ARM 、现场总线、 PLC 、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 3
    2021-4-20 13:07
    2518 次阅读|
    3 个评论
    当RS485隔离遇见Freemodbus,你碰到问题了吗?
    摘要: 基于 RS485 物理接口的 Modbus 总线在工业控制中广泛应用。 Freemodbus 是一个免费的实用的协议栈,孔丙火(微信公众号:孔丙火)用它实现了一个从站。串口收发信号和 RS485 芯片的收发使能信号采用光耦进行了隔离,在测试过程发现了偶尔出现 TimeOut 的问题,经过查找及分析,发现是光耦信号延时导致的,给出了解决方案,并对问题进行了详细分析。 关键词: RS485 隔离 Freemodbus 光耦 时序 1. 问题来源 工业应用中,在进行 RS485 电路设计的时候,为了确保电气安全和抗干扰性,经常会做信号隔离,光耦隔离是常用的一种形式。 Modbus 协议是现场总线协议中比较常见的一种,其设计和使用简单,运行可靠,在可编程控制器、仪器仪表、传感器中使用广泛,其中,基于 RS485 物理接口的 Modbus 总线使用最多。 Modbus 是主从式的通信结构,一个系统中仅有一个主站,其他为从站。 Freemodbus 是一个免费的协议栈,仅支持从站。 Freemodbus 有专门的机构在维护,比较成熟,相对于自己编写的协议代码,运行更稳定,是开发者在研发时的常用选项。 近来,孔丙火(微信公众号:孔丙火)在设计一个基于 Modbus-RTU 接口的信号采集站,作为从站,把采集到的信号传输给 PLC 等主站设备。单片机采用 STM32F103C8T6 , RS485 芯片: SP485EEN ,接收和发送的隔离采用光耦 PC410 , 485 收发使能信号的隔离采用光耦 EL357NB 。 RS485 部分的原理图如图 1 所示。 图 1 程序设计中,采用 Freemodbus 协议栈实现 Modbus-RTU 协议,串口速率为 115200bps ,数据位 8 位,停止位 1 位,无校验。 在使用 Modbus 主站软件( Modbus Poll )进行调试的过程中,孔丙火(微信公众号:孔丙火)发现通信过程中会出现 TimeOut 的错误,有以下几个特点: a )并不是每一帧都会出错,但会不定时地出现; b )主站询从站的频率越高,出错的几率越低,主站每 30ms 询一帧数据的时候基本不出错,但每 500ms 或 1000ms 询一次的时候,就会比较明显地出错。 2. 查找过程 由于硬件电路是之前使用过的,刚开始并没有考虑是电路的问题。首先是从软件开始查找的。由于能够正常的回复数据,只是偶尔出错,因此软件的整体流程应该是通的,只是某个代码的细节有问题,孔丙火(微信公众号:孔丙火)把代码整个重新捋了一遍,也没有发现明显的问题。 后来,开始逐步排查,先把 RS485 部分的电路短接掉,用 USB-TTL 转换器直接连接单片机的串口收发管脚,进行收发数据的测试,结果一切都正常,无论主站的询问数据周期是多少,都不会出错。于是开始怀疑跟 485 相关的代码。 搞过 485 电路的朋友都知道, 485 电路仅仅是实现一个电平转换,另外由于 485 是半双工,需要外加一个收发使能控制, 485 芯片 实现 TTL 电平与差分电平的转换。涉及到代码,就是多一个管脚,用来控制收发使能。虽然这一步没有测试出问题,但在反复试的过程中,倒是有一点意外收获,孔丙火(微信公众号:孔丙火)分享一下。使能 485 芯片的发送语句,必须在使能串口发送中断(发送为空中断)之前,否则通信是无法成功的。原因分析:发送为空中断在使能后,是立即进入中断的,使能 485 发送的代码无法执行,发送没有使能,但已开始发送数据,这种情况下,发送是肯定不会成功的。这个点给我了启发,是不是发送的时序有问题,导致发送失败呢? 于是开始分析 485 的收发电路,由于信号是隔离的,最有可能出现问题的地方是光耦,就从光耦开始查起。开始查阅 PC410 和 EL357NB 的数据手册,开始是担心光耦前后的限流电阻跟光耦的电流传输比是不是不匹配,导致光耦导通不充分,后经分析电路参数没有问题。然后开始分析光耦的时序,经过光耦隔离的信号肯定会延时,延时的不同步会不会导致问题呢?果然, PC410 和 EL357NB 的传输延时还是有差别的, PC410 的传输延时在几十 ns ,而 EL357NB 的传输延时在 1us 左右,程序代码做了如下修改:在使能 485 发送管脚后,延时一段时间后( 1us 左右,无需太精确),再使能串口的发送中断,经测试, TimeOut 的错误了。 3. 结论及分析 ( 1 )问题就出在光耦的信号传输延时上,在 485 电路的设计之初,单片机的串口收发脚采用高速光耦进行隔离,收发使能脚由于不需要频繁切换,采用一般光耦即可。正是由于这个设计,在使能 485 收发和使能发送中断同时执行的时候,在单片机的串口发送部分(从发送缓存区到硬件管脚,单片机内部完成,无需用户代码干预)处理比较快的时候,就会导致 485 还没有使能发送状态,单片机已开始向 485 芯片发送数据,就会导致发送出错, Modbus 主站软件( Modbus Poll )收到的是不完整的错误的帧,就是看到的 TimeOut 错误。但在单片机的串口发送部分处理的不那么快的时候,就不会出现这个错误,这是错误不定时出现的原因。 ( 2 ) 使能 485 发送管脚后的延时,不能采用 HAL_Delay() 函数, Freemodbus 协议栈的收发使能在 vMBPortSerialEnable() ,由于在调此函数之前已经禁掉了中断,而 HAL_Delay() 函数是基于系统时钟中断定时的,因此不能使用。由于此处不需要精确地定时,只要能满足两种光耦延时的时间差即可,可以使用空语句进行定时,大体算一下时间即可。孔丙火(微信公众号:孔丙火)的代码如图 2 所示。 图 2 ( 3 )在最初的问题中,为什么主站询的快的时候错误少,反而询的慢的时候错误多?这个问题没有完全思考清楚,但孔丙火(微信公众号:孔丙火)有一些心得。元器件都会有一些寄生电容,在电平转换的时候,可以认为电容要先进行充电或放电,因此会有一个斜坡,在主站询的快的时候,电平转换快,在电平改变的时候,电容在上一次的过程中还没有完全放电或充电完毕,这个时候进行反方向的改变需要的时候就会短,可以对冲两种光耦时间差的影响。不知道我这种说法大家是否可以理解,更深层次或者更确切的原因,也欢迎高手指点,或者大家行进讨论。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 3
    2021-4-17 18:09
    2947 次阅读|
    1 个评论
    用两张图,详解FreeModbus在单片机串口上的数据收发过程-FreeModbus从站设计(9)
    FreeModbus从站设计(9)-详解FreeModbus在单片机串口上的数据收发过程 关键词:FreeModbus STM32F103 CubeMX HAL库 串口 1.引言 在上一篇文章中,主要阐述了vMBPortSerialEnable()这个函数如何基于HAL库调度单片机串口的收发,感觉还是不是很清晰,因此,孔丙火(微信公众号:孔丙火)在这一篇文章中,重点捋一下串口的收发函数调用关系,以求有有一个清晰的脉络。 2.函数调用的基本框架 直接上图,更清晰,接收过程如图1所示,发送过程如图2所示。 图1 接收过程起源于vMBPortSerialEnable()函数的调用,此时,该函数将串口设置位接收状态,即使能接收中断,禁止发送中断。从图中可以清晰的看出,需要修改的地方就是接收中断的回调函数和portserial.c和porttimer.c中的几个函数。至于何时调用vMBPortSerialEnable()函数,孔丙火(微信公众号:孔丙火)认为,我们是不需要关心的,只要按照之前的文章,把FreeModbus的代码添加到keil工程中,FreeModbus协议栈会进行调度。接收过程是一个字节一个字节进行接收的,当协议栈检测到定时器超时,则认为一个完整的数据帧接收完毕,开始进入数据处理的阶段,数据处理完成后,则进行回复数据的发送。 图2 发送过程同样起源于vMBPortSerialEnable()函数的调用,此时,该函数将串口设置位发送状态,即使能发送中断,禁止接收中断。从图中可以清晰的看出,需要修改的地方就是发送中断的回调函数和portserial.c中的几个函数。至于何时调用vMBPortSerialEnable()函数,孔丙火(微信公众号:孔丙火)认为,我们是不需要关心的,只要按照之前的文章,把FreeModbus的代码添加到keil工程中,FreeModbus协议栈会进行调度。发送过程同样是一个字节一个字节进行的,在xMBRTUTransmitFSM()函数中,会检测是否还有需要发送的数据,若没有数据需要发送了,则会调用vMBPortSerialEnable()函数,再次将串口设置为接收状态。作为Modbus从站,串口大部分时间是处于接收状态的。 3.总结 在这篇文章中,孔丙火(微信公众号:孔丙火)接着上一篇文章的思路,用两张图把FreeModbus在单片机串口上数据收发流程进行了梳理,脉络更加清晰。有了这样一个思路,可以更好地理解,移植FreeModbus的时候,为什么需要修改portserial.c和porttimer.c中的函数,和为什么需要修改串口中断的回调函数。从这篇文章中,也可以看出,采用HAL库是比较简单的,像是中断处理这些内容库函数都已经处理好了,很方便,可以提高开发效率。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。