STM32开发项目:FreeMODBUS库的扩展与增强
日期 | 作者 | 版本 | 说明 |
---|---|---|---|
2020.09.24 | Tao | V0.0 | 完成主体内容的撰写 |
特别说明
本文是笔者关于modbus长期开发经验的总结,分成几个阶段逐步完成,中间可能掺杂了英文。由于时间原因,全文也没有统一语言,还请读者谅解。本文介绍的移植方法、功能增强、扩展库都是笔者亲自编写设计并经过大量工程验证的,但其中依然可能存在漏洞与Bug,也不能保证在读者的项目或者平台中能够顺利实施。它山之石,可以攻玉,希望笔者的这篇文章对读者起到参考借鉴的作用。
FreeMODBUS简介
一般情况下,我们不会从底层开始去实现(也很难实现)完整的MODBUS协议栈,而会“站在巨人的肩膀上”,直接采用移植第三方的modbus库进行开发,而笔者今天要介绍的FreeMODBUS库的扩展与增强就是基于应用广泛的第三方库。网上关于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为例,笔者介绍一下移植过程中需要注意的几个关键问题:
官方源码
- Download freemodbus source code.
- 源码结构如下表所示
序号 | 名称 | 说明/描述 |
---|---|---|
1 | ascii | 这个文件夹包含Modbus-ASCII协议的实现代码 |
2 | functions | 这个文件夹主要包括一些功能码对应的处理函数 |
3 | include | 里面主要是Modbus协议需要使用的一些头文件和配置文件 |
4 | rtu | 这个文件夹包含Modbus-RTU协议的实现代码 |
5 | tcp | 这个文件夹包含Modbus-TCP协议的实现代码 |
6 | mb.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:
void USART1_IRQHandler(void)
{
/**
* 如果使能串口接收中断,那么ORE为1时也会产生中断。
* 在应用中对ORE标志进行处理,当判断发生ORE中断的时候,
* 我们再读一次USART_DR的值,
* 这样如果没有新的Overrun 溢出事件发生的时候,ORE会被清除,
* 然后程序就不会因为ORE未被清除而一直不断的进入串口中断
*/
if(USART_GetFlagStatus(USART1, USART_FLAG_ORE) != RESET)
{
USART_ReceiveByte(USART1);
}
#if FREEMODBUS_ENABLE==1 && FREEMODBUS_PORT_NUM == 1
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
pxMBFrameCBByteReceived();
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
{
pxMBFrameCBTransmitterEmpty();
USART_ClearITPendingBit(USART1, USART_IT_TC);
}
else
{
}
#else
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
/*省略了与移植无关代码*/
}
#endif
}
If USART1 is chosen as modbus serial port, you should define:
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)
{
#if FREEMODBUS_ENABLE==1 && FREEMODBUS_TIMER == 2
pxMBPortCBTimerExpired();
#else
Timer2Updated();
#endif
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
- 由于使用的是串口发送完成中断(USART_IT_TC),想要进入该中断服务函数,需要发送一个字节的数据并启动串口发送中断,代码还需要少许修改。在rtu/mbrtu.c 的eMBRTUSend中稍作修改,代码如下:
eMBErrorCode eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{
eMBErrorCode eStatus = MB_ENOERR;
USHORT usCRC16;
ENTER_CRITICAL_SECTION( );
/* Check if the receiver is still in idle state. If not we where to
* slow with processing the received frame and the master sent another
* frame on the network. We have to abort sending the frame.
*/
if( eRcvState == STATE_RX_IDLE )
{
/* First byte before the Modbus-PDU is the slave address. */
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
usSndBufferCount += usLength;
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
/* Activate the transmitter. */
eSndState = STATE_TX_XMIT;
//移植插入的代码 启动第一次发送,这样才可以进入发送完成中断
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;
usSndBufferCount--;
vMBPortSerialEnable( FALSE, TRUE );
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
- Rewrite assert function.
Freemodbus use assert function to report errors in the process of communication, which was declared in assert.h:
# 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:
- Change macro
assert
toassert_mb
, such as:
assert_mb( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
- Define macro
assert_mb
and declare functionUser_MbAssertFunc
in mbplatform.h:
# define assert_mb(__e) ((__e) ? (void)0 : User_MbAssertFunc(__FILE__, __LINE__, __ASSERT_FUNC, #__e))
extern void User_MbAssertFunc(const char *file, int line, const char *function, const char *error);
- Define function
User_MbAssertFunc
in mb_user.c:
/**
* @brief 当freemodbus中出错时,调用此函数进行错误处理
* @param file: 一般情况下,填入出错所在的文件 __FILE__
* @param line: 一般情况下,填入出错所在的行 __LINE__
* @param function: 一般情况下,填入__ASSERT_FUNC
* @param error: freemodbus库中传入出错参数(直接转化为字符串)
*/
void User_MbAssertFunc(const char *file, int line, const char *function, const char *error)
{
/*not realize yet*/
}
-
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.
增强与扩展的功能
使用示例
本文地址:https://blog.csdn.net/u013441358/article/details/108025365