tag 标签: 单片机

相关帖子
相关博文
  • 热度 6
    2021-6-19 22:31
    872 次阅读|
    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-17 18:09
    608 次阅读|
    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、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 11
    2021-3-21 21:57
    822 次阅读|
    0 个评论
    FreeModbus 从站设计( 8 ) - 用 HAL 库函数理清 Modbus 的数据收发流程 关键词: FreeModbus STM32F103 CubeMX HAL 库 1. 基本框图 如图 1 所示, HAL 库的函数中,与 Freemodbus 协议栈相关的,主要是定时器和串口的操作部分。孔丙火(微信公众号:孔丙火)认为,可以这样简单描述:在协议栈完成初始化后,就将串口( RS485 )设置为接收状态,等待主站的数据,当接收到主站的一个字节的数据后,开启定时器,在 3.5 个字符周期内如果接收到了第二个字节的数据,则将定时器清零重新开始计时,若果 3.5 个字符周期内没有接收到新的字节,则认为一帧数据接收完毕,开始处理数据,并相应地发送回复数据,回复数据也是逐个字节进行发送的。 图 1 2. 接口函数 2.1 vMBPortSerialEnable() 这个函数定义在 portserial.c 中,所有的调用均在 mbrtu.c 和 mbascii.c 中,由于这里我们只实现 RTU ,因此,只关注 mbrtu.c 中调用。在 mbrtu.c 中,共有 5 处调用此函数,分别在 eMBRTUStop() 、 eMBRTUStop() 、 eMBRTUSend() 、 xMBRTUTransmitFSM() 中,这个函数的作用,就是使能或禁止串口的接收或发送中断,状态的转换是根据协议栈的状态机来进行的,孔丙火(微信公众号:孔丙火)认为,看一看 Modbus 协议文本中的状态机图,有助于深入理解协议的运行。基于以上理解, vMBPortSerialEnable() 函数的代码如下: 图2 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1) ,这个函数是 HAL 库函数,孔丙火(微信公众号:孔丙火)认为,可以这样理解,以中断方式通过串口 2 接收一个字节的数据,存在变量 ucUsrUart2Rxbuf 中,这里取得是 ucUsrUart2Rxbuf 的地址指针。 __HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE) 这个函数是禁止接收中断。在这个函数下面,写了这样一个语句: huart2.RxState = HAL_UART_STATE_READY; 同样的,在发送部分有: huart2.gState = HAL_UART_STATE_READY; 下面简单阐述一下作用。 最初开始弄 FreeModbus 移植的时候,在网上查阅了一些资料,有的是基于 HAL 库的,有的不是,有的是交叉使用,由于学习了一段时间的 HAL 库,感觉其还是比较易用的,孔丙火(微信公众号:孔丙火)就想着能不能完全用 HAL 库的函数来实现的 FreeModbus 移植,于是就有后来的实践。 加这样一个语句,是为了解决在实际调试过程中碰到的问题。刚开始调试的时候发现,单片机作为从站,只能接收一帧数据,第 2 帧以后的数据,不再有反应。由于是调用 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1); 来使能接收非空中断,调用 __HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE); 来禁用接收非空中断,那就要从 HAL_UART_Receive_IT() 这个函数开始说起,在以前的文章中,孔丙火(微信公众号:孔丙火)说过,这个函数本质上是一个配置函数,配置好接收缓冲区的指针和一次接收的字节数,把这个函数贴出来看一下: 图 3 可以看出,这个函数的主要功能就是设置接收缓存的指针和字节数,然后使能接收非空中断,但在一开始有一个条件判断, RxState ,这其实是对串口接收状态的一个管理,当串口中断方式接收已经被配置过了、接收过程还没有完成的时候,是不能再次配置的,防止接收数据出错。 在这个函数中,配置完了以后,就将 RxState 的状态置为了 HAL_UART_STATE_BUSY_RX ,这个状态是在串口中断处理函数中完成数据接收后,重新设为 HAL_UART_STATE_READY 。在本文的程序中,当从站接收数据完成后(定时器动作,表示一个完整的帧接收完成),协议栈需要将接收非空中断禁掉,但这之前串口仍处于接收状态(调用过 HAL_UART_Receive_IT(&huart2,&ucUsrUart2Rxbuf,1) ),因此 RxState 的状态是 HAL_UART_STATE_BUSY_RX ,这样在下次转为接收时,无法 HAL_UART_Receive_IT ()来进行配置,就是由于上面提到的图 3 红框中的条件判断。因此,在发现此问题后,加了这个语句,确保后续能正常接收,并且这样可以不用修改 HAL 库本身的函数。发送部分是同样的道理。 下一篇精彩继续。 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、 PLC 、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
  • 热度 4
    2021-3-21 14:07
    9339 次阅读|
    1 个评论
    实战项目 -- 做一个舵机控制上位机
    实战项目旨在交流学习,项目相关资料请在 关注大鸟科创空间微信公众号后 回复项目关键字 ———“舵机控制” 以获取,欢迎在评论区交流意见。 简介:设计舵机控制的上位机和运行在舵机控制板中的下位机代码,实现联动控制舵机。 先展示效果: 项目开发环境: Visual Studio 2017/ Arduino IDE 开发语言: C#/C demo功能: 1:设计上位机,使得用户在其界面拖动滑条,可以通过串口发送指令到单片机控制板 2:设计单片机控制板中的固件程序,接收上位机发过来的串口指令并解析,根据指令控制舵机运动 3:这里单片机控制板采用一块自己设计的arduino板,其他单片机板也可以,主要理解其通信原理。 PS:这个上位机设计在前期的博文中有介绍,详细设计过程可参考前期内容,下面上源码截图: 上位机部分源码截图: 还有下位机部分源码截图: 上位机要注意串口的设置, demo 设计的比较简单,仅仅实现了原理,在舵机控制方面,有很多玩法,包括将指定动作写入控制板内并保存在芯片 flash 中,实现重播;也可以在线编辑动作组;而舵机控制板还可以用 stm32 或者 51 等单片机设计电路和程序,这些内容将在后期陆续分享开源。 至此项目介绍完毕,本项目旨在开源,想要获取源码资料的朋友,关注大鸟科创空间微信公众号后,回复项目关键字 ———“舵机控制” ,即可获得源码资料下载链接。 微信扫描下方二维码,关注“大鸟科创空间”微信公众号
  • 热度 4
    2021-3-20 13:07
    862 次阅读|
    0 个评论
    STM32F103、FreeModbus从站设计(7)-如何让RTU的定时器正常工作起来 关键词:Modbus FreeModbus STM32F103C8T6 CubeMX 移植 1.基本原理 在CubeMX工程配置中,已经将定时器2(TIM2)的时钟周期(可以理解为心跳一下)设为50us,Counter Period(产生中断)暂时设为了35,也就是说50us×35=1750us产生一次中断,这个时间就是判断RTU中帧间隔的标准。但在Freemodbus协议栈(遵循Modbus国标)中,这个时间不是固定的,在波特率小于19200bps时,需要具体计算这个时间,当波特率大于或等于19200bps时,这个时间固定为1750us,如图1所示。因此,孔丙火(微信公众号:孔丙火)认为,把它设为固定值是不方便的,当波特率修改的时候,还要单独修改此参数,可以用一个变量来设置此参数,变量的值随波特率而改变,这也有利于后期通信参数修改的程序。 图1 2.代码修改 在mbrtu.c中,有一个eMBRTUInit()函数,在协议栈初始化的时候被调用,在图2中可以看出,t3.5的计算方法。计算完了以后,调用xMBPortTimersInit()这个函数,这个函数在porttimer.c中,如果参数是写死的,CubeMX本身已经生成了定时器的初始化函数,这个函数是不用写的,直接返回True就行了。孔丙火(微信公众号:孔丙火)这里需要根据这个计算结果修改通信参数,因此要用一下这个函数。 图2 在modbus_app.c中定义一个全局变量uint16_t usUsrTimeOutCount;//modbus定时器计数周期(50us的个数),在xMBPortTimersInit()中,将usTimerT35_50us的值赋给usUsrTimeOutCount,然后在函数MX_TIM2_Init()中用这个值对TIM2进行初始化。xMBPortTimersInit()的代码如图3所示。MX_TIM2_Init()修改后的代码如图4所示。孔丙火(微信公众号:孔丙火)提醒,这样修改之后,在main.c中MX_TIM2_Init()这个函数的调用必须在eMBInit( MB_RTU, ucUsrSlaveAddress, 1, ulUsrBaudRate, eUsrParity );之后。在main.c中的初始化阶段的调用关系如图5所示。 图3 图4 图5 文章在公众号( 孔丙火 )同步推出,欢迎查看更多系列文章。 单片机、ARM、现场总线、PLC、嵌入式软硬件的设计经验分享,秉承“点点滴滴皆智慧”的理念,以实际项目为单元阐述知识点,一起分享,共同交流。
相关资源
广告