从github下载:https://github.com/cwalter-at/freemodbus

无法下载或者下载太慢可以用资源下载,无需积分。[freeModbus从机源码下载]

示例代码

一、使用资源需求

要实现FreeModbus的移植,必须在硬件上符合FreeModbus的使用要求。FreeModbus对硬件的需求很小,基本上任何具有支持接收与发送中断的串口,一个能够产生RTU传输所需的T35定时器,并且有能够容纳数据帧的RAM(实际的内存需求取决于所使用的地址的多少)的微控制器就足够了。而在软件上,对于实现FreeModbus,仅仅需要一个简单的事件队列。在使用操作系统情况下,可通过单独创建一个任务完成Modbus事件的轮询。

下面以STM32f103为例做移植参考。

二、单片机工程创建

使用STM32CubeMX创建一个新的工程,我们在这里主要使用的是串口2。

freemodbus协议 freemodbus 主机_IT

然后我这里配置使用了FREERTOS。具体配置就不详细描述,网上也有很多教程案例。或者可以下载我上传的资源进行对照。不关心这一步的也可以直接移植到裸机程序工程中。

freemodbus协议 freemodbus 主机_stm32_02


资源中的CUBE文件

freemodbus协议 freemodbus 主机_IT_03


生成的MDK_V5工程

freemodbus协议 freemodbus 主机_单片机_04

三、源代码移植

将FreeModbus从机源代码拷贝到工程中间件-第三方库-freemodbus源码库:即Middlewares\Third_Party\FreeModbusSlaver文件夹中。

freemodbus协议 freemodbus 主机_freemodbus协议_05


将源码添加到工程中

freemodbus协议 freemodbus 主机_stm32_06


头文件包含

freemodbus协议 freemodbus 主机_嵌入式硬件_07

3.1 源码接口完善

FreeModbus的移植主要包含:物理层接口的修改、应用层回调的修改。具体函数如下:

3.1.1 物理层接口:
(a) portserial.c
1)xMBPortSerialInit()
2)vMBPortSerialEnable()
3)xMBPortSerialPutByte()
4)xMBPortSerialGetByte()
(b) porttimer.c
1)xMBPortTimersInit()
2)vMBPortTimersEnable()
3)vMBPortTimersDisable()
3.1.2 应用层回调:
1)eMBRegCoilsCB()
2)eMBRegDiscreteCB()
3)eMBRegInputCB()
4)eMBRegHoldingCB()
3.1.3 具体修改代码
串口设置
1) void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)

此函数的功能为设置串口状态。有两个参数:xRxEnable及xTxEnable。当xRxEnable为真时,应使能串口接收;当xTxEnable为真时,应使能串口发送。

2) BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)

此函数的功能是初始化UART。有四个参数:ucPORT、ulBaudRate、ucDataBits及eParity。参数ucPORT可以忽略;参数ulBaudRate是波特率;参数ucDataBits为通讯时所使用的数据位宽,采用RTU模式,因此将其固化;eParity为校验方式,采用无校验方式,函数返回值务必为TRUE。
在这里我们如果采用的是STM32Cube生成的代码,无需在这了初始化,只需要直接返回TRUE即可,当然也可以将串口初始化移至此处。

3) BOOL xMBPortSerialPutByte(CHAR ucByte)与BOOL xMBPortSerialGetByte( CHAR * pucByte )

BOOL xMBPortSerialPutByte(CHAR ucByte)的功能为发送一字节数据。

/*
 * FreeModbus Libary: ATMega168 Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *   - Initial version and ATmega168 support
 * Modfications Copyright (C) 2006 Tran Minh Hoang:
 *   - ATmega8, ATmega16, ATmega32 support
 *   - RS485 support for DS75176
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

#include "usart.h"

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

#define UART_BAUD_RATE          9600

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
	if (xRxEnable)	
	{
		__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);	
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);
	}
	if (xTxEnable)
	{
		__HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE);
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE);
	}
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	/*¿ÉÔÚ´Ë´¦Ìí¼Ó´®¿Ú³õʼ»¯º¯ÊýMX_USART2_UART_Init();Ò²¿ÉÒÔÔÚmainº¯Êý¼¯Öгõʼ»¯*/
    return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
	if(HAL_UART_Transmit (&huart2 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )
		return FALSE ;
	else
      return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
	if(HAL_UART_Receive (&huart2 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )
		return FALSE ;
	else
		return TRUE;
}

/* Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call 
 * xMBPortSerialPutByte( ) to send the character.
 */
void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/* Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}


#ifdef RTS_ENABLE
SIGNAL( SIG_UART_TRANS )
{
    RTS_LOW;
}
#endif
定时器设置
5) BOOL xMBPortTimersInit( USHORT usTim1Timerout )

此函数功能为初始化定时器。参数为:usTim1Timerout。根据所使用的硬件初始化定时器,使之能产生中断时间为usTim1Timerout个时钟的中断。

6) void vMBPortTimersEnable()与void vMBPortTimersDisable()

void vMBPortTimersEnable()的功能为使能超时定时器。需在此清零定时器计数值,并重新计数。
void vMBPortTimersDisable()的功能为关闭超时定时器。需在此函数中关闭定时器中断。

/*
 * FreeModbus Libary: ATMega168 Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- AVR includes -------------------------------------*/
#include "tim.h"

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    /* Calculate overflow counter an OCR values for Timer1. */
	/*¿ÉÔÚ´Ë´¦Ìí¼ÓTIM6³õʼ»¯º¯ÊýMX_TIM6_Init();Ò²¿ÉÒÔÔÚmainº¯Êý¼¯Öгõʼ»¯*/
	
    return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
	__HAL_TIM_ENABLE_IT(&htim6,TIM_IT_UPDATE);
	__HAL_TIM_SET_COUNTER(&htim6,0);
	__HAL_TIM_ENABLE(&htim6);
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	__HAL_TIM_DISABLE(&htim6);
	__HAL_TIM_SET_COUNTER(&htim6,0);
	__HAL_TIM_DISABLE_IT(&htim6,TIM_IT_UPDATE);
	__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}
回调函数修改

Modbus在应用层实现上,采用功能码的形式对不同数据类型进行操作,具体功能码定义如图

freemodbus协议 freemodbus 主机_IT_08

mbSlaverCB.h

在回调函数头文件中定义
输入寄存器 起始地址 寄存器数量
保持寄存器 起始地址 寄存器数量
线圈寄存器 起始地址 寄存器数量

#define MB_SLAVER_TEST	1

/* REGADDRS AND NUMBER DEFINE */
#define REG_INPUT_START 			5000
#define REG_INPUT_NREGS 			5

#define REG_HOLDING_START       	5400
#define REG_HOLDING_NREGS       	5

#define REG_COIL_START          	5900
#define REG_COIL_NREGS          	16

#define REG_DISC_START_STANDARD     5000
#define REG_DISC_NREGS_STANDARD     48

#define REG_DISC_START_EXPAND     	5100
#define REG_DISC_NREGS_EXPAND     	96

#define REG_DISC_START_INNER     	5400
#define REG_DISC_NREGS_INNER     	32
mbSlaverCB.c

定义测试使用的输入寄存器,并初始化其值
static uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x11, 0x22, 0x33, 0x44, 0x55};

#include "mbSlaverCB.h"

static uint16_t usRegInputStart = REG_INPUT_START;
#if MB_SLAVER_TEST
	static uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x11, 0x22, 0x33, 0x44, 0x55};
#else
	static uint16_t usRegInputBuf[REG_INPUT_NREGS];
#endif
	
/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
	eMBErrorCode    eStatus = MB_ENOERR;
	
    if( ( usAddress >= REG_HOLDING_START )
        && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
		switch(eMode)
		{
			case MB_REG_READ:
			{
				
				break;
			}
			case MB_REG_WRITE:
			{
				
				break;
			}
			default:
				break;
		}
	}
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

以输入寄存器回函数为例,在上位机使用0x04功能码读输入寄存器时,整个Modbus的状态图如图2所示,在正确解析支持的功能码后进入eMBRegInputCB()函数,此函数所做的操作即为图中起始地址usAddress及usAddress+ usNRegs合法判断,并在合法的情况下处理读输入寄存器请求。代码如下:

freemodbus协议 freemodbus 主机_stm32_09


同理,其它3个函数针对不同的功能码及起始地址及数量合法判断结果进行对应的操作,函数处理过程大致相同。

工程文件修改
stmf1xxit.c(中断文件)
/**
  * @brief This function handles USART2 global interrupt.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
//HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
	if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_ORE))
	{
		uint16_t pucByte = (uint16_t)((&huart2)->Instance->DR & (uint16_t)0x01FF);
		__HAL_UART_CLEAR_OREFLAG(&huart2);
	}
	if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET) 
	{
		prvvUARTRxISR();		//½ÓÊÕÖжÏ
	}
	if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET) 
	{
		prvvUARTTxReadyISR();	//·¢ËÍÖжÏ
	}
	if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
	{
		__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);
	}
	if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC))
	{
		__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TC);
	}
  	HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
//  HAL_UART_IRQHandler(&huart2);
  /* USER CODE END USART2_IRQn 1 */
}
/**
  * @brief This function handles TIM6 global interrupt.
  */
void TIM6_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_IRQn 0 */

  /* USER CODE END TIM6_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_IRQn 1 */
	/* NOTE : This function Should not be modified, when the callback is needed,
		the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
	*/
	prvvTIMERExpiredISR( );
  /* USER CODE END TIM6_IRQn 1 */
}
freertos.c

生成的项目文件将任务初始化都生成再freertos.c文件中,配置时STM32Cube工程时,配置了一个Mb_Slaver_Thread的任务。修改任务代码如下:

/* USER CODE END Header_Mb_Slaver_Thread */
void Mb_Slaver_Thread(void *argument)
{
  /* USER CODE BEGIN Mb_Slaver_Thread */
	eMBInit( MB_RTU, 0x01, 2, 115200, MB_PAR_NONE);//初始化MODBUS
	eMBEnable(  );//使能modbus
	/* Infinite loop */
	for(;;)
	{
		( void )eMBPoll(  );//启动MODBUS侦听
		osDelay(1);
	}
  /* USER CODE END Mb_Slaver_Thread */
}

注意:实际使用中可以将任务代码放在单独的文件中。此外如果不使用操作系统,可以在裸机程序中初始化调用。

四、FreeModbus初始化及运行流程

FreeModbus是通过检测相应的消息来完成对应的功能。协议栈的初始化及运行流程如下:

  1. 调用eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )完成物理层设备的初始化,主要包括:BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )串口初始化;BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )定时器初始化,设定T35定时所需要的定时器常数。
  2. 调用eMBErrorCode eMBEnable(void)使能协议栈,主要包括:static pvMBFrameStart pvMBFrameStartCur(函数指针)协议栈开始,将eRcvState设为STATE_RX_INIT状态,调用void vMBPortSerialEnable(BOOL xRxEnable,BOOL xTxEnable)使能接收,调用void vMBPortTimersEnable()使能超时定时器。
  3. Modbus_App任务调用eMBPoll(void)轮询事件。
  4. 若发生串口接收中断,且eRcvState为STATE_RX_IDLE,则向接收缓存中存入接收到的字符,同时将eRcvState设为STATE_RX_RCV状态,并清零超时定时器。在下一个数据来到时,不断将数据存入接收缓存,并清零超时定时器。
  5. 如果没有接收完成,则不可能发生超时中断。发生超时中断,说明T35时间内未收到新的串口数据,根据Modbus协议的规定,这指示着一帧请求数据接收完成。接收完成后发送消息EV_FRAME_RECEIVED,等待处理此消息。
  6. eMBPoll(void)检测到EV_FRAME_RECEIVED,调用peMBFrameReceiveCur获得从机地址、数据及数据长度。
  7. 从数据帧中获取功能码,根据相应的功能码查找处理该功能码的函数指针来处理该功能。若不是广播消息,则调用peMBFrameSendCur发送回复消息。

五、FreeModbus验证

使用modbus上位机mbpoll轮训输入寄存器,并且能够修改其值,验证成功。

freemodbus协议 freemodbus 主机_stm32_10


(此处提供工具示意,写文档时连接使用)

六、示例代码

STM32F103ZET6_TESTCODE