请选择 进入手机版 | 继续访问电脑版
搜索
查看: 1127|回复: 2

[分享] STM32开发项目:FreeMODBUS库的扩展与增强

[复制链接]

该用户从未签到

1492

主题

2703

帖子

0

蝴蝶豆

管理员

最后登录
2021-5-11
发表于 2020-9-29 12:52:23 | 显示全部楼层 |阅读模式
FreeMODBUS简介

一般情况下,我们不会从底层开始去实现(也很难实现)完整的MODBUS协议栈,而会“站在巨人的肩膀上”,直接采用移植第三方的modbus库进行开发,而笔者今天要介绍的FreeMODBUS库的扩展与增强就是基于应用广泛的第三方库。网上关于FreeMODBUS的介绍与移植教程有很多,质量参差不齐。


FreeMODBUS 是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。在FreeMODBUS的当前版本中,提供了Modbus Application Protocol v1.1a 的实现并且支持在Modbus over serial line specification 1.0中定义的RTU/ASCII传输模式。从0.7版本开始,FreeModbus也支持在TCP defined in Modbus Messaging on TCP/IP Implementation Guide v1.0a中定义的TCP传输。Freemodbus遵循BSD,这意味着本协议栈的实现代码可以应用于商业用途。目前版本的FreeModbus支持如下的功能码:



  • 读输入寄存器 (0x04)
  • 读保持寄存器 (0x03)
  • 写单个寄存器 (0x06)
  • 写多个寄存器 (0x10)
  • 读/写多个寄存器 (0x17)
  • 读取线圈状态 (0x01)
  • 写单个线圈 (0x05)
  • 写多个线圈 (0x0F)
  • 读输入状态 (0x02)
  • 报告从机标识 (0x11)



本实现基于最新的标准并且与标准完全兼容。接收和传输Modbus RTU/ASCII数据帧是通过一个由硬件提取层的调用来驱动状态机来实现的。这就使得该协议非常容易移植到其他的平台之上。当收到一个完整的数据帧后,该数据帧被传入Modbus应用层,数据帧的内容在该层得到解析。为例方便增加新的Modbus功能,Freemodbus在应用层通提供了Hooks。


如果用到了Modbus TCP协议,那么当准备处理一个新数据帧的时候,移植层就必须首先向协议栈发送一个事件标志。然后,协议栈调用一个返回值为接收到的Modbus TCP数据帧的函数,并且开始处理这个数据帧。如果数据有效,则相应的Modbus反馈帧将提供给移植层生成反馈帧。最后,该反馈被发送到客户端。


一般来说FreeMODBUS主要应用在小型嵌入式系统与单片机中(不运行操作系统或者运行RTOS),而在通用操作系统平台上(Linux, MacOs, Windows),我们一般使用libmodbus库进行modbus通讯相关的开发。同样的,笔者推荐去它的官网libmodbus库的官网获取一手资料和最新源码。以下也给出一些摘自官网的介绍:


A Modbus library for Linux, Mac OS X, FreeBSD, QNX and Win32

Libmodbus is a free software library to send/receive data according to the Modbus protocol. This library is written in C and supports RTU (serial) and TCP (Ethernet) communications.The license of libmodbus is LGPL v2.1+ and the licence of programs in the tests directory is BSD 3-clause.



移植FreeMODBUS的几个关键问题

以移植到STM32F103为例,笔者介绍一下移植过程中需要注意的几个关键问题:



官方源码
  • 源码结构如下表所示


序号名称说明/描述
1ascii这个文件夹包含Modbus-ASCII协议的实现代码
2functions这个文件夹主要包括一些功能码对应的处理函数
3include里面主要是Modbus协议需要使用的一些头文件和配置文件
4rtu这个文件夹包含Modbus-RTU协议的实现代码
5tcp这个文件夹包含Modbus-TCP协议的实现代码
6mb.c这个是MODBUS协议栈的主文件,这个文件夹只是一个框架,与具体的协议无关

因为modbus有三种具体的协议,分别为RTU、ASCII和TCP,具体的实现在1、4、5文件夹中,而mb.c在初始化的时候会根据使用情况将指针指向具体的处理函数。



移植流程

移植的时候需要重点关注

  • Add freemodbus/modbus, freemodbus/platform, freemodbus/registers and freemodbus/user folders to your own stm32 library. Among the above four folders:


    • freemodbus/modbus is the source code folder of the FreeMODBUS, which is downloaded from the internet.
    • The remaining three folders freemodbus/platform, freemodbus/registers and freemodbus/user are created by the users.

  • In freemodbus/modbus folder, only rtu/mbrtu.c need some modification if you configuration the USART_IT_TC interruption, which will be detailed in step 5.

  • Modify portserial.c and porttimer.c in freemodbus/platform (modifications for stm32f103 platform have been completed already). (Follow the steps described in this blog)

  • Rewrite IRQ handler of modbus serial port and timer in other places of your project. For example, if USART1 is chosen as modbus serial port, you should define:



  1. void USART1_IRQHandler(void)
  2. {
  3.         /**
  4.          * 如果使能串口接收中断,那么ORE为1时也会产生中断。
  5.          * 在应用中对ORE标志进行处理,当判断发生ORE中断的时候,
  6.          * 我们再读一次USART_DR的值,
  7.          * 这样如果没有新的Overrun 溢出事件发生的时候,ORE会被清除,
  8.          * 然后程序就不会因为ORE未被清除而一直不断的进入串口中断
  9.          */
  10.         if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
  11.         {
  12.                 USART_ReceiveByte(USART1);
  13.         }

  14. #if FREEMODBUS_ENABLE==1 && FREEMODBUS_PORT_NUM == 1
  15.         if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
  16.         {
  17.                 pxMBFrameCBByteReceived();
  18.                 USART_ClearITPendingBit(USART1, USART_IT_RXNE);
  19.         }
  20.         else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
  21.         {
  22.                 pxMBFrameCBTransmitterEmpty();
  23.                 USART_ClearITPendingBit(USART1, USART_IT_TC);
  24.         }
  25.         else
  26.         {

  27.         }
  28. #else
  29.         if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
  30.         {
  31.                 /*省略了与移植无关代码*/
  32.         }
  33. #endif
  34. }
复制代码

If USART1 is chosen as modbus serial port, you should define:

  1. void TIM2_IRQHandler(void)
  2. {
  3.         if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
  4.         {
  5. #if FREEMODBUS_ENABLE==1 && FREEMODBUS_TIMER == 2
  6.                 pxMBPortCBTimerExpired();
  7. #else
  8.                 Timer2Updated();
  9. #endif

  10.                 TIM_ClearFlag(TIM2,TIM_FLAG_Update);
  11.                 TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
  12.         }
  13. }
复制代码


由于使用的是串口发送完成中断(USART_IT_TC),想要进入该中断服务函数,需要发送一个字节的数据并启动串口发送中断,代码还需要少许修改。在rtu/mbrtu.c 的eMBRTUSend中稍作修改,代码如下:


  1. eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
  2. {
  3.         eMBErrorCode    eStatus = MB_ENOERR;
  4.         USHORT          usCRC16;

  5.         ENTER_CRITICAL_SECTION(  );
  6.    
  7.        /* Check if the receiver is still in idle state. If not we where to
  8.         * slow with processing the received frame and the master sent another
  9.         * frame on the network. We have to abort sending the frame.
  10.         */
  11.        if( eRcvState == STATE_RX_IDLE )
  12.        {
  13.            /* First byte before the Modbus-PDU is the slave address. */
  14.            pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
  15.            usSndBufferCount = 1;
  16.    
  17.            /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
  18.            pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
  19.            usSndBufferCount += usLength;
  20.    
  21.            /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
  22.            usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
  23.            ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
  24.            ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
  25.    
  26.            /* Activate the transmitter. */
  27.            eSndState = STATE_TX_XMIT;
  28.    
  29.            //移植插入的代码 启动第一次发送,这样才可以进入发送完成中断
  30.            xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
  31.            pucSndBufferCur++;
  32.            usSndBufferCount--;
  33.    
  34.            vMBPortSerialEnable( FALSE, TRUE );
  35.        }
  36.        else
  37.        {
  38.            eStatus = MB_EIO;
  39.        }
  40.        EXIT_CRITICAL_SECTION(  );
  41.        return eStatus;
  42.    }
复制代码

Rewrite assert function.
Freemodbus use assert function to report errors in the process of communication, which was declared in assert.h:


  1. # define assert(__e) ((__e) ? (void)0 : __assert_func (__FILE__, __LINE__, __ASSERT_FUNC, #__e))
复制代码

This function is may not be suitable for our own project. As a result, it is better to re-implement the error reporting function. There are 6 places that need to be modified:


20200923202144694_meitu_2.jpg



  • Change macro assertto assert_mb, such as:

  1. assert_mb( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
复制代码



  • Define macro assert_mband declare function User_MbAssertFuncin mbplatform.h:
  1. # define assert_mb(__e) ((__e) ? (void)0 : User_MbAssertFunc(__FILE__, __LINE__, __ASSERT_FUNC, #__e))

  2. extern void User_MbAssertFunc(const char *file, int line, const char *function, const char *error);
复制代码


  • Define function User_MbAssertFuncin mb_user.c:


  1. /**
  2. * @brief 当freemodbus中出错时,调用此函数进行错误处理
  3. * @param file: 一般情况下,填入出错所在的文件 __FILE__
  4. * @param line: 一般情况下,填入出错所在的行 __LINE__
  5. * @param function: 一般情况下,填入__ASSERT_FUNC
  6. * @param error: freemodbus库中传入出错参数(直接转化为字符串)
  7. */
  8. void User_MbAssertFunc(const char *file, int line, const char *function, const char *error)
  9. {
  10.         /*not realize yet*/
  11. }
复制代码


  • freemodbus/registers is the collection of operation functions of four types of registers. There are eight files in the folder: mb_coilsreg.c, mb_coilsreg.h, mb_discretereg.c, mb_discretereg.h, mb_holdingreg.c, mb_holdingreg.h, mb_inputreg.c, mb_inputreg.h. All these files belongs to the extended content of the freemodbus.

  • freemodbus/user is the collection of project related functions of four types of registers. This folder is the most frequently modified location based on the project. There are two files in the folder: mb_user.c, mb_user.h. All these files belongs to the extended content of the freemodbus.







回复

使用道具 举报

该用户从未签到

5

主题

161

帖子

30

蝴蝶豆

高级会员

最后登录
2024-2-2
发表于 2021-1-22 11:24:38 | 显示全部楼层
看完了,还是和我的思路对接不上。
感觉青蛙掉井里了“不懂”!
回复 支持 反对

使用道具 举报

该用户从未签到

14

主题

237

帖子

3

蝴蝶豆

金牌会员

最后登录
2023-1-28
发表于 2021-1-25 10:37:58 | 显示全部楼层
早就移植用上了~~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条

Archiver|手机版|小黑屋|论坛-意法半导体STM32/STM8技术社区

GMT+8, 2024-3-29 17:58 , Processed in 1.160289 second(s), 34 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表