一:平台工具资源介绍
二:使用CubeMX创建基础工程
三:添加FreeModbus软件包到工程
四:适配FreeModbus
五:测试

前言

这是基于STM32从站的FreeModbus-rtu移植,使用hal库。

一:软硬件工具资源介绍

1、CubeMX6.4.0

2、FreeeModbus源码1.5.0版本

3、STM32G474控制板

4、keil5.36

二:使用CubeMX创建基础工程

1、配置串口

freemodbus 主机源码 freemodbus stm32_IT


freemodbus 主机源码 freemodbus stm32_IT_02

2、配置定时器

freemodbus 主机源码 freemodbus stm32_freemodbus 主机源码_03


freemodbus 主机源码 freemodbus stm32_stm32_04

三:添加FreeModbus软件包到工程

1、源码文件介绍,并提取使用到的文件

新建一个名为STM32MB的文件夹。然后打开freeModbus代码包的demo文件夹,之后将BARE文件夹内所有内容复制到STM32MB文件夹下,复制完成如图

freemodbus 主机源码 freemodbus stm32_freemodbus 主机源码_05


回到freeModbus代码包,复制整个modbus文件夹也粘贴到STM32MB文件夹内,完成效果如图

freemodbus 主机源码 freemodbus stm32_stm32_06


将STM32MB文件夹移动到stm32cubeMX生成的工程目录下,如图

freemodbus 主机源码 freemodbus stm32_freemodbus 主机源码_07

2、在keil新建FreeModbus分支文件夹 FreeModbus_Port 、FreeModbus_Source并从源码添加文件

打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,MB内添加STM32MB文件夹下modbus文件夹内所有c文件以及根目录的demo.c文件,MB_Port内添加STM32MB文件夹下port文件夹内所有c文件,如图所示

freemodbus 主机源码 freemodbus stm32_IT_08

四:适配FreeModbus

源码修改三个文件,串口portserial.c;定时器porttimer.c;port.h;

1、串口适配

修改MB_Port下的portserial.c文件(串口设置)

#include "port.h"
#include "stm32g4xx_hal.h" // 增加自己的头文件
#include "usart.h"  // 增加自己的头文件
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

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

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
		if (xRxEnable)															//将串口收发中断和modbus联系起来,下面的串口改为自己使能的串口
			{
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);  // 485方向设置为输入
				__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);	//我用的是串口1,故为&huart1
			}
		else
			{
				HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);// 485方向设置为输出
				__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
			}
		if (xTxEnable)
			{
				__HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);
			}
		else
			{
				__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
			}		
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return TRUE; //  改为 TRUE 因为cubemx已经初始化了
}

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. */
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET); // 485方向设置为输出
	
		if(HAL_UART_Transmit (&huart1 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )	//添加发送一位代码
		{
    	    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);		// 485方向设置为输入	
		
			return FALSE ;
		}
		else
		{
    	    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET);	// 485方向设置为输入
			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 (&huart1 ,(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 )//删去前面的static,方便在串口中断使用
{
    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 )//删去前面的static,方便在串口中断使用
{
    pxMBFrameCBByteReceived(  );
}

修改串口全局中断服务函数

// 在/* USER CODE BEGIN PFP */后添加以下代码,用于和modbus的串口和定时器功能代码联系
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)!= RESET) 
		{
			prvvUARTRxISR();//接收中断
		}

	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)!= RESET) 
		{
			prvvUARTTxReadyISR();//发送中断
		}
	
  HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE END USART1_IRQn 1 */
}

2、定时器适配

修改MB_Port下的porttimer.c文件(定时器设置)

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "stm32g4xx_hal.h"  // 增加自己的头文件
#include "tim.h"  // 增加自己的头文件
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

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

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us ) //定时器初始化直接返回TRUE,已经在mian函数初始化过
{
    return TRUE;
}


inline void
vMBPortTimersEnable( void ) //使能定时器中断,我用的是定时器4,所以为&htim4
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
		__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);
		__HAL_TIM_ENABLE_IT(&htim4,TIM_IT_UPDATE);
		__HAL_TIM_SetCounter(&htim4,0);
		__HAL_TIM_ENABLE(&htim4);	
}

inline void
vMBPortTimersDisable( void )//取消定时器中断
{
    /* Disable any pending timers. */
			__HAL_TIM_DISABLE(&htim4);
			__HAL_TIM_SetCounter(&htim4,0);
			__HAL_TIM_DISABLE_IT(&htim4,TIM_IT_UPDATE);
			__HAL_TIM_CLEAR_IT(&htim4,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 )//modbus定时器动作,需要在中断内使用
{
    ( void )pxMBPortCBTimerExpired();
}

修改定时器全局中断服务函数

/**
  * @brief This function handles TIM4 global interrupt.
  */
void TIM4_IRQHandler(void)
{
  /* USER CODE BEGIN TIM4_IRQn 0 */

  /* USER CODE END TIM4_IRQn 0 */
//  HAL_TIM_IRQHandler(&htim4);
  /* USER CODE BEGIN TIM4_IRQn 1 */

    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE)) // ?????????
    {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);// ??????
        prvvTIMERExpiredISR();	// ??modbus3.5????????
    }

  /* USER CODE END TIM4_IRQn 1 */
}

3、port.h

修改完Modbus与stm32的接口文件之后要在port.h文件内定义总中断,位置在port.h文件的32行和33行,修改为如下所示,并在port.h前包含上stm32的hal库,如图所示

#ifndef _PORT_H
#define _PORT_H

#include <assert.h>
#include <inttypes.h>
#include "stm32g4xx_hal.h"

#define	INLINE                      inline
#define PR_BEGIN_EXTERN_C           extern "C" {
#define	PR_END_EXTERN_C             }

#define ENTER_CRITICAL_SECTION( )   __set_PRIMASK(1) 	 //关总中断
#define EXIT_CRITICAL_SECTION( )    __set_PRIMASK(0)   //开总中断


typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char CHAR;

typedef uint16_t USHORT;
typedef int16_t SHORT;

typedef uint32_t ULONG;
typedef int32_t LONG;

#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

#endif

五:编写modbus功能处理函数,并测试

硬件接口方面结束之后就可以开始写功能了,在MB–>demo.c中有功能示例,我们根据功能示例来修改对应的功能并使能modbus,这里只说输入寄存器功能,其它的一次类推,就不多赘述。就是自己设置一个数组,将数据放到数组内,并在被读取时根据数据位置将数据返回去。

1、