基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植


参考与简介:

说在最前面,我是通过前面两位老哥的资料整合,站在了巨人的肩膀上完成了移植,以此说明一下。

链接:基于STM32CubeMX移植freeModbusRTU(从站)

链接:基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植

在移植之前准备了一下材料:

硬件:

正点原子STM32F407探索者开发板

下载器STlink

数据线Minusb转USB

上位机调试助手:

mbpoll(提取码:ns74)

软件:

STM32CubeMX

FreeModbus官方源码包


一、STM32CubeMX初始环境配置

1.Project

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第1张图片

2.MCU

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第2张图片

在1处输入自己的MUC型号,随后双击2处对应型号的芯片

3.Reset and Clock Controler

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第3张图片

外部时钟源RCC选项中,1表示禁用,2表示旁路时钟源,3表示石英/陶瓷 晶振,此处选择3石英/陶瓷 晶振

4.System

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第4张图片

Debug选项由于是使用STlink下载所以选择Serial Wire

我们需要使能FreeRtos,所以此处将HAL库的1ms定时中断采用的定时器改为TIM10,因为Systick要用作FreeRTOS的时基定时

5.Clock Configuration

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第5张图片

采用外部时钟源,主频配置了168MHz

6.Timer

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第6张图片

已知在freemodbus中默认定义:当波特率大于19200时,判断一帧数据超时时间固定为1750us,当波特率小于19200时,超时时间为3.5个字符时间。这里移植的是115200,所以一帧数据超时时间为1750us

这里采用TIM2,TIM2挂载在ABP1上,主频为84MHz。我们此处取预分频系数84-1,可以得到对应的分频频率为1MHz,即0.000001s=0.001ms=1us,自动重载值设置为1750,得到超时时间1750us

7.USART

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第7张图片

采用异步通信方式,通讯参数此处为默认值,按需要更改。

8.FreeRtos

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第8张图片

在1处选择FreeRtos版本,有CMSIS_V1和CMSIS_V2两个版本可供选择

Tasks and Queues一栏中可创建任务函数,双击2处:

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第9张图片

Task Name:任务名

Priority:任务优先级

Stack Size:分配堆栈大小

Entry Function:任务函数名

9.NVIC

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第10张图片

使能定时器2和串口1的中断

10.GPIO

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第11张图片

探索者开发板上PF9,PF10两个引脚控制LED灯,所以此处将PF9,PF10设置为输出引脚

11.Porject Manager

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第12张图片

此处工程文件名自定义,IDE选择MDK-ARM,注意工程路径中不要有中文,不然STM32CubeMX会在生成项目时会报错

12.Code Generator

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第13张图片

12.GENERATE CODE

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第14张图片

到此处基础STM32Cubemx工程文件配置完成


二、FreeModbus工程代码移植

1.将FreeModbus官方源码移植到上述配置的工程文件中

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第15张图片

在工程目录文件夹下新建一个HardWare文件夹用于存放外设的库文件

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第16张图片

 打开事先下载好的freemodbus-v1.6文件夹,需要此次移植需要用到的为上图中所标注出的两处。

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第17张图片

在HardWare文件夹下新建一个STModBus文件夹用于存放freemodbus需要移植的文件

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第18张图片

将freemodbus-v1.6文件夹下modbus文件夹整体复制到STModBus中

然后把freemodbus-v1.6\demo\BARE文件夹下的port、demo.c以及Makefile同样也复制到STModBus中

2.修改Keil5中工程配置文件

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第19张图片

个人习惯,不打钩就可以节省不少编译时间,但是无法使用Go To Definition这个功能

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第20张图片

由于STM32CubeMx生成的工程文件默认是不勾选Reset and Run,导致生成了代码,编译也通过了,0 Error(s),0 Waring(s) ,但是点击load的时候程序却不运行,需要手动复位一下。

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第21张图片

点击此处,增加STModBus以及STModBus_Port这两个组

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第22张图片

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第23张图片

将Modbus_demo\HardWare\STModBus\modbus目录下所有.c文件以及Modbus_demo\HardWare\STModBus目录下demo.c文件导入STModBus组

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第24张图片

将Modbus_demo\HardWare\STModBus\port目录下portevent.c、portserial.c、porttimer.c文件导入STModBus_Port组

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第25张图片

添加完毕后如上图所示

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第26张图片

将前面移植的文件夹添加到头文件路径中

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第27张图片

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第28张图片

修改demo.c的main函数名为host,然后编译,此时已经可以编译通过。

3.FreeModBus配置以及修改

(1)修改portserial.c文件

#include "port.h"
/* ----------------------- STM32 includes ----------------------------------*/
#include "stm32f4xx_hal.h"
#include "usart.h"

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

首先引入所需要的头文件

/* ----------------------- static functions ---------------------------------*/
//static void prvvUARTTxReadyISR( void );
//static 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)															
			{
				__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);
			}	
}

将FreeModBus的串口收发底层函数与STM32串口收发相关联,此处使用是USART1,故为&huart1

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return TRUE;
}

将原先的FALSE修改为TRUE,此函数是用于初始化串口,由于在生成工程文件时已经初始化完串口1了,所以此处直接返回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 )	
			return FALSE ;
		else
			return TRUE;
}

添加在UARTs传输缓冲区中放置一个字节的代码

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

添加返回UARTs接收缓冲区中的字节的代码

//static 
void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}


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

把static修饰词注释,方便在串口中断使用

(2)修改porttimer.c文件

#include "port.h"
/* ----------------------- STM32 includes ----------------------------------*/
#include "stm32f4xx_hal.h"
#include "tim.h"

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

同样首先引入所需要的头文件

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

将此处的静态函数声明注释掉

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    return TRUE;
}

将原先的FALSE修改为TRUE,此函数是用于初始化定时器,由于在生成工程文件时已经初始化完定时器了,所以此处直接返回TRUE就行了

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_SET_COUNTER(&htim2,0);
		__HAL_TIM_ENABLE(&htim2);
}

使能定时器中断,此处使用的是TIM2,所以为&htim2

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

禁用任何挂起的定时器

//static 
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

把static修饰词注释,方便在中断内使用

(3)修改port.h文件

#ifndef _PORT_H
#define _PORT_H

#include 
#include 
#include "stm32f4xx_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

同样首先引入所需头文件:

#include "stm32f4xx_hal.h"

然后添加开/关总中断代码:

#define ENTER_CRITICAL_SECTION( )   __set_PRIMASK(1)

#define EXIT_CRITICAL_SECTION( )    __set_PRIMASK(0)

(4)修改stm32f4xx_it.c文件

/* USER CODE BEGIN 1 */

//在这BEGIN和END中间为规定的用户区!

/* USER CODE END 1 */

由于STM32CubeMX在每次生成代码后会将不按规范要求编写代码的区域对代码做删除处理,所以在对STM32CubeMX所生成的文件进行修改时,切记要在用户区内编写用户代码!切记要在用户区内编写用户代码!切记要在用户区内编写用户代码!重要的事说三遍!

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
/* USER CODE END PFP */

使用extern声明这两个外部函数,用于和modbus的串口底层代码相关联

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

添加将USART1收到的内容转移到freemodbus的功能函数进行处理

(5)修改demo.c文件

大致理一下整个demo.c文件:

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

此处没什么好说的,引入所需的头文件

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 10

这里定义了两个标识符表示的常量

#define REG_INPUT_START 0          输入寄存器的起始地址为0
#define REG_INPUT_NREGS 10       输入寄存器的个数为10

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;

uint16_t   usRegInputBuf[REG_INPUT_NREGS];
uint16_t   InputBuff[5];

额外定义了两个保存输入寄存器数据的数组

//int
//host( 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]++;
//    }
//}

将之前修改的host函数注释,此处并非是程序运行的main函数,后面将其移植到main.c中即可

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;
	
		int             i;
		InputBuff[0] = 0x11;
		InputBuff[1] = 0x22;
		InputBuff[2] = 0x33;
		InputBuff[3] = 0x44;
	
    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--;
//        }
				for(i=0;i>8;
					pucRegBuffer++;
					*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
					pucRegBuffer++;
				}
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

(6)修改main.c文件

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.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 */

同样,首先引入所需的头文件

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);

/* USER CODE BEGIN PFP */
extern void prvvTIMERExpiredISR( void );
/* USER CODE END PFP */

 使用extern声明prvvTIMERExpiredISR外部函数,用于和modbus的定时器底层代码相关联

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);
	eMBEnable(  );
  /* USER CODE END 2 */

在所需外设初始化完成之后的用户编辑区内初始化FreeModBus函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM10) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */
	if(htim->Instance == TIM2)
	{
		prvvTIMERExpiredISR( );
	}
  /* USER CODE END Callback 1 */
}

添加定时器中断回调函数

(7)修改freertos.c文件

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

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

同样,首先引入所需的头文件

void LED_Task(void *argument)
{
  /* USER CODE BEGIN LED_Task */
  /* Infinite loop */
  for(;;)
  {
		eMBPoll();
        osDelay(10);
  }
  /* USER CODE END LED_Task */
}

 在LED_Task任务中启动modbus侦听


前一段时间忙于工作没更新,实际上到这整体移植方案已经算完成了,接下来做一个简单的收尾。

程序下载后,开发板通过UAB线与电脑连接

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第29张图片

 电脑端打开Modbus Poll 软件-->连接设置-->连接,原则相应配置,确保与自己程序要能对应上,然后点击确认

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第30张图片

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第31张图片

 点击软件上方的设置-->读写定义,如下配置

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第32张图片

 已经能正确接收到输入寄存器的值

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第33张图片

然后依次进行如下操作

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第34张图片

把数值显示改成16进制,这样看的会更加明显,

基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植_第35张图片

 这样收尾就算完成了~


我这边主要使用保持寄存器,简单贴一下代码,有不清楚评论或私信联系

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;
//		return MB_ENOREG;
}

以下 这是新增一个FreeRTOS任务,做了一些简单的读写。

void StartTask02(void *argument)
{
  /* USER CODE BEGIN StartTask02 */
  /* Infinite loop */
	UCHAR  pucRegBuffer1[4] = {0xFF,0x01,0x02,0x03};
	UCHAR  pucRegBuffer2[4];
		
  for(;;)
  {
		eMBRegHoldingCB(&pucRegBuffer1[0],1,1,MB_REG_WRITE);
		eMBRegHoldingCB(&pucRegBuffer2[0],2,1,MB_REG_READ);
		if( pucRegBuffer2[0] == 0xF1 &&  pucRegBuffer2[1] == 0x1F)
		{
			eMBRegHoldingCB(&pucRegBuffer1[2],3,1,MB_REG_WRITE);
		}
    osDelay(200);
  }
  /* USER CODE END StartTask02 */
}

 实验效果为读写保持寄存器,

寄存器0值为0xFF01,

如果寄存器1值等于0xF11F,

则会向寄存器2写入0x0203;

这是在正点原子探索者开发板上测试没问题的程序,有需要的自己下载,提取码(qk6x)

你可能感兴趣的:(stm32,单片机,嵌入式,freertos,modbus)