基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植

基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植


移植前准备工作(不限于以下方式):
硬件
开发板
USB转串口工具
仿真器/下载器

上位机
Modbus Poll

开发环境
STM32CubeMX
MDK KEIL5

Freemodbus移植包
freemodbus-v1.6.zip

工程下载链接
freemodbus_RTU移植工程(stm32f405_CubeMX不带操作系统)+调试工具+freemodbus-V1.6.zip


一、CubeMX的配置

1.配置系统时钟
这里采用内部时钟源,主频配置了168MHz。
在这里插入图片描述
2.配置UART串口
这里使用串口1,对应引脚为PA9和PA10。
在这里插入图片描述
使能串口1全球中断
在这里插入图片描述
波特率设置为115200,其他默认即可
在这里插入图片描述
3.配置定时器
在freemodbus中默认有一个这样的定义:当波特率大于19200时,判断一帧数据超时时间固定为1750us,当波特率小于19200时,超时时间为3.5个字符时间。我们这里是115200,所以一帧数据超时时间为1750us。

这里我们采用TIM2,TIM2挂载在ABP1上,主频为84MHz。(4200/84)*35=1750us
故我们这里分频取4200,周期取35,向上计数。
在这里插入图片描述

使能TIM2全球中断
在这里插入图片描述

(4)产生代码
注意生产代码的路径不能包含中文。
在这里插入图片描述

C文件和H文件分开,生成代码,至此,CubeMX配置完成
在这里插入图片描述

二、freeMODBUS移植

1.移植文件
打开之前生成的工程目录,新建一个USER文件夹
在这里插入图片描述

在USER文件夹里再新建一个freeMODBUS文件夹,用于存放移植要用到的文件
在这里插入图片描述
打开下载好的官方freemodbus-v1.6文件夹,freemodbus-v1.6 ----> demo ---->BARE,将BARE文件下的3个文件复制到之前新建的freeMODBUS文件夹下。
在这里插入图片描述
打开下载好的官方freemodbus-v1.6文件夹,将modbus文件夹也复制到之前新建的freeMODBUS文件夹下。完成后,新建的freeMODBUS文件夹内容如下:
在这里插入图片描述
2.MDK_KEIL文件配置
添加.H文件路径
在这里插入图片描述
添加.C文件
这里建议先添加这两个组后就点击OK,等这两个组成功添加到工程中后,再来添加C文件。我这个MDK keil一次操作会卡死。

在这里插入图片描述
添加完c文件后是这样的
在这里插入图片描述
3.修改代码
(1)修改freeMODBUS_port下的portserial.c文件,顾名思义,这个c文件和串行端口有关。
首先,我们添加必要的头文件,main.h和usart.h

#include "port.h"

//添加头文件
#include "main.h"
#include "usart.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

然后,我们将prvvUARTTxReadyISR( void )和prvvUARTRxISR( void )这两个中断服务函数的static类型屏蔽掉,因为后面在别的文件中我们还需要调用这两个函数。

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

这里的两个static类型也屏蔽掉,从这里可以看出,这里两个中断服务函数实际分别调用的是pxMBFrameCBTransmitterEmpty( )和pxMBFrameCBByteReceived( )。

/* 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.
 */

//static
 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.
 */

//static 
 void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

那么,这两个中断服务函数怎么用?在哪里用?
prvvUARTTxReadyISR( void ),顾名思义,其实就是串口发送中断,prvvUARTRxISR( void )就是串口接受中断。

打开stm32f4xx_it.c文件,修改串口1中断处理函数,如下所示,那么上面说的两个函数就用在这里。

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  	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);
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */



  /* USER CODE END USART1_IRQn 1 */
}

在main.h文件中,extern外部声明这两个函数,以免报错

/* USER CODE BEGIN EFP */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);

回到portserial.c文件,添加以下代码,显然这个函数是控制串口收发中断的使能和失能的。

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
    	if (xRxEnable)															
			{
				__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);	
			}
		    else
			{
				__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);
			}
}

接着,将xMBPortSerialInit函数中的FALSE改为TRUE,这个函数是用来初始化串口的,因为CubeMX生成工程时串口就已经初始化好了,所以这里就直接返回TRUE。
最后修改xMBPortSerialPutByte和xMBPortSerialGetByte函数,这两个函数的功能分别是发送和接受一个字节,按如下修改。

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return TRUE ;  //改为 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 (&huart1 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK) //发送1个字节
      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 (&huart1 ,(uint8_t *)pucByte,1,0x01) != HAL_OK ) //接收1个字节
      return FALSE ;
   else
      return TRUE;

}

(2) 修改porttimer.c文件,顾名思义,这个文件和定时器相关
添加main.h和tim.h这两个头文件

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
//添加头文件
#include "main.h"
#include "tim.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

同样,将这两个地方的static注释掉,因为别处需要调用这个服务函数

/* ----------------------- static functions ---------------------------------*/
//static 
    void prvvTIMERExpiredISR( void );
/* 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.
 */
//static
    void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

那么prvvTIMERExpiredISR( void )这个函数在哪调用呢?

在stm32f4xx_it.c文件的/* USER CODE BEGIN 1 */下添加定时器中断回调函数,并在里面添加prvvTIMERExpiredISR( )函数。

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)	//定时器中断回调函数,用于连接porttimer.c文件的函数
{
  /* NOTE : This function Should not be modified, when the callback is needed,
            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
      if(htim->Instance == htim2.Instance)
    {
     prvvTIMERExpiredISR( );
    }
}

/* USER CODE END 1 */

当然,需要在main.h里外部声明该函数

/* USER CODE BEGIN EFP */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );
/* USER CODE END EFP */

回到porttimer.c文件,修改代码如下

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


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

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
    HAL_TIM_Base_Stop_IT(&htim2);
    __HAL_TIM_SetCounter(&htim2,0);
    __HAL_TIM_CLEAR_IT(&htim2,TIM_IT_UPDATE);
}

(3) 修改demo.c文件
打开demo.c文件,将int main这段函数注释掉。

/* ----------------------- Start implementation -----------------------------*/

//int
//main( void )
//{
//    eMBErrorCode    eStatus;

//    eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );

//    /* Enable the Modbus Protocol Stack. */
//    eStatus = eMBEnable(  );

//    for( ;; )
//    {
//        ( void )eMBPoll(  );

//        /* Here we simply count the number of poll cycles. */
//        usRegInputBuf[0]++;
//    }
//}

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;
}

我们看到上面有这样一个函数eMBRegInputCB,这个函数默认已经添加过了,不用再添加。顾名思义,这个函数与输入寄存器相关。那么这个就对应了modbus的0x04的功能。
然后修改一下这里,REG_INPUT_START是输入寄存器的起始寄存器,这里我改为1,REG_INPUT_NREGS是输入寄存器的总数,usRegInputBuf[REG_INPUT_NREGS] 就是保存输入寄存器数据的数组。这里随便改了一下。这里关于modbus输入寄存器的部分就这样了。保持寄存器和输入寄存器类似。

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

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1
#define REG_INPUT_NREGS 5

#define REG_HOLDING_START 1
#define REG_HOLDING_NREGS 10
/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS] = {0x12,0x34,0x56,0x78,0x90};

static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS]={0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00};

这里,我们再添加一下保持寄存器的函数具体功能,按如下修改,剩下的eMBRegCoilsCB 和eMBRegDiscreteCB就不再添加了,可自行添加。

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
           eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_HOLDING_START ) && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegHoldingStart );
        switch ( eMode )
        {
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

(4) 修改port.h文件
打开port.h头文件,按下面修改这两个地方,并添加main.h头文件

#ifndef _PORT_H
#define _PORT_H

#include <assert.h>
#include <inttypes.h>
#include "main.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)   //开总中断    

(5)修改main.c文件
打开main.c文件,添加代码如下,就是将上面注释掉的代码部分按如下添加

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */



   eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_EVEN );//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。

    /* Enable the Modbus Protocol Stack. */
   eMBEnable(  );


  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
  
    /* USER CODE BEGIN 3 */
     eMBPoll(  );
  }
  /* USER CODE END 3 */
}

并在main.c添加mb.h和mbport.h这两个头文件

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */

编译整个工程,0错误,0警告。至此,freeMODBUS移植工作基本完成
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421183105257.png

三、测试

烧录好代码,连接好板子,电脑打开Modbus Poll调试软件,Setup–>F8,按如下设置
在这里插入图片描述
Connection–>F3,按如下设置
在这里插入图片描述
连接成功后,如下所示,成功读取保持寄存器数据
在这里插入图片描述
Display下拉最后一个可打开收发面板
注意:质量不好的USB转串口工具可能会丢帧

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值