基於STM32CubeMX移植freeModbusRTU(從站)

困惑了將近一年多的ModbusRTU在我昨天窮極無聊給自己定目標的情況下搞出來了,以前移植不出來主要原因就是基本功不紮實,沒有進一步理解串口和定時器配置的原理,一通操作,移植完之後就Timeout,接下來就分享一下我是怎麼從0開始移植這個協議的。

項目已上傳碼雲,文章底部有鏈接!

1.需要的材料

  1. STM32開發板一塊,不限型號
  2. freeModbus包可進入後方鏈接下載(Modbus官方源碼包
  3. STM32CubeMX

2.操作步驟

操作之前先講兩個主要問題

1.串口設置問題

MoubusRTU移植到stm32平臺通信是通過串口進行通信,主要是需要串口進行收發,所以發送中斷時必須的,在波特率設置問題上是和定時器相關聯,在mbrtu.c文件的eMBRTUInit函數裏具體說明了串口波特率和定時器設置的關係

eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    ULONG           usTimerT35_50us;

    ( void )ucSlaveAddress;
    ENTER_CRITICAL_SECTION(  );

    /* Modbus RTU uses 8 Databits. */
    if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
    {
        eStatus = MB_EPORTERR;
    }
    else
    {
        /* If baudrate > 19200 then we should use the fixed timer values
         * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
         */
        if( ulBaudRate > 19200 )
        {
            usTimerT35_50us = 35;       /* 1750us. */
        }
        else
        {
            /* The timer reload value for a character is given by:
             *
             * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
             *             = 11 * Ticks_per_1s / Baudrate
             *             = 220000 / Baudrate
             * The reload for t3.5 is 1.5 times this value and similary
             * for t3.5.
             */
            usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
        }
        if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
        {
            eStatus = MB_EPORTERR;
        }
    }
    EXIT_CRITICAL_SECTION(  );

    return eStatus;
}

從上面代碼的註釋中可以看出,當波特率大於19200時,超時時間固定位爲1750us,當波特率小於19200時,超時時間爲3.5個字符時間,具體計算公式在代碼註釋裏已經有了,這裏我就不多贅述。本人波特率使用115200,所以按照1750us來。

2.定時器設置問題

ModbusRTU是通過定時器和串口配合來實現Modbus通信的,所以定時器是決定有沒有超時的一大關鍵問題,由串口設置部分可知,定時器設置是要配合串口設置的波特率食用比較香,所以根據我使用的115200波特率可以得到我定時器設置。首先是APB1的主頻率獲取到,modbus要求通過預分配後得到的週期爲50us,對應頻率爲20KHz。根據rtu初始化代碼得到自動重載值設置爲35。

具體操作:


熟悉stm32cubemx的老司機可以直接從15步看起


1.選擇MCU型號

選擇自己的stm32

2.使能時鐘源RCC爲外部時鐘時鐘源選擇
3.配置時鐘樹,記錄APB1頻率,我這裏是36MHz

配置時鐘樹

4.使能定時器4,預分頻係數爲1800-1,對應的分頻頻率爲20KHz,不懂的回到上面去看定時器設置解析,自動重載值設置爲35,得到超時時間1750us。

使能定時器4

5.使能定時器中斷

在這裏插入圖片描述

6.配置串口2,選擇異步通信後參數設置爲115200,8,NONE,1

在這裏插入圖片描述

7.使能串口中斷

串口中斷

8.配置中斷優先級,定時器中斷優先級低於串口中斷即可

中斷優先級

9.配置項目參數並分離頭文件和c文件後生成代碼。

在這裏插入圖片描述
在這裏插入圖片描述

10.打開freeModbus代碼包的demo文件夾,新建一個名爲STM32MB的文件夾,之後將BARE文件夾內所有內容複製到STM32MB文件夾下,複製完成如圖

在這裏插入圖片描述

11.回到freeModbus代碼包,複製整個modbus文件夾也粘貼到STM32MB文件夾內,完成效果如圖

在這裏插入圖片描述

12.將STM32MB文件夾移動到stm32cubeMX生成的工程目錄下,如圖

在這裏插入圖片描述

13.打開工程,引入STM32MB內的所有頭文件,並新建名爲MB和MB_Port的組,MB內添加STM32MB文件夾下modbus文件夾內所有c文件以及根目錄的demo.c文件,MB_Port內添加STM32MB文件夾下port文件夾內所有c文件,如圖所示

工程文件
添加頭文件

14.修改demo.c文件夾的main函數名爲host,編譯不報錯即可開始修改,如圖所示

在這裏插入圖片描述
以下爲正式修改Modbus代碼,上面比較繁瑣,熟悉stm32cubemx的老司機可以直接從15步看起

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

我直接貼代碼,自己對比我的代碼和源碼差距,關鍵地方我會在後邊標註

#include "port.h"
#include "stm32f7xx_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)															//將串口收發中斷和modbus聯繫起來,下面的串口改爲自己使能的串口
			{
				__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);	//我用的是串口2,故爲&huart2
			}
		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 )
{
    return TRUE;				//改爲TURE,串口初始化在usart.c定義,mian函數已完成
}

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.
 */
//static 
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.
 */
//static 
void prvvUARTRxISR( void )				//刪去前面的static,方便在串口中斷使用
{
    pxMBFrameCBByteReceived(  );
}

16.修改MB_Port下的porttimer.c文件(定時器設置)

我直接貼代碼,自己對比我的代碼和源碼差距,關鍵地方我會在後邊標註

#include "port.h"
#include "stm32f7xx_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 )		//定時器初始化直接返回TRUE,已經在mian函數初始化過
{
    return TRUE;
}


inline void
vMBPortTimersEnable(  )		//使能定時器中斷,我用的是定時器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(  )	//取消定時器中斷
{
    /* 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.
 */
//static 
void prvvTIMERExpiredISR( void )	//modbus定時器動作,需要在中斷內使用
{
    ( void )pxMBPortCBTimerExpired(  );
}
17.修改完Modbus與stm32的接口文件之後要在port.h文件內定義總中斷

位置在port.h文件的32行和33行,修改爲如下所示,並在port.h前包含上stm32的hal庫,如圖所示

#define ENTER_CRITICAL_SECTION( )   __set_PRIMASK(1) 	 //關總中斷
#define EXIT_CRITICAL_SECTION( )    __set_PRIMASK(0)   //開總中斷
#include "stm32f7xx_hal.h"

在這裏插入圖片描述
modbus端口函數到此修改完成,接下來是中斷函數

18.串口及定時器中斷修改

打開工程內的中斷文件,是在Application/User–>stm32f7xx_it.c
根據板子不同而不同,區別是stm32f後面的數字。知道是中斷管理文件就行
在/* USER CODE BEGIN PFP */後添加以下代碼,用於和modbus的串口和定時器功能代碼聯繫

extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );

找到自己設置的串口中斷處理函數,添加如下代碼,用於將串口收到的內容移動到modbus功能函數進行處理

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_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET) 
		{
			prvvUARTRxISR();//接收中斷
		}

	if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET) 
		{
			prvvUARTTxReadyISR();//發送中斷
		}
	
  HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE END USART2_IRQn 1 */
}

在Application/User–>stm32f7xx_it.c末尾的/* 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
   */
  	prvvTIMERExpiredISR( );
}

到此,串口和定時器的問題已經處理完畢,接下來是modbus的配置

19.modbus功能處理

硬件接口方面結束之後就可以開始寫功能了,在MB–>demo.c中有功能示例,我們根據功能示例來修改對應的功能並使能modbus,這裏我只說輸入寄存器功能,其它的一次類推,就不多贅述。
這裏也是直接貼代碼,大概說一下,就是自己設置一個數組,將數據放到數組內,並在被讀取時根據數據位置將數據返回去

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

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

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
//static 
uint16_t   usRegInputBuf[REG_INPUT_NREGS];
uint16_t InputBuff[5];

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 );
				for(i=0;i<usNRegs;i++)
				{
					*pucRegBuffer=InputBuff[i+usAddress-1]>>8;
					pucRegBuffer++;
					*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
					pucRegBuffer++;
				}
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

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


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

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

20.modbus啓動

啓動modbus需要在main函數進行初始化、開啓偵聽操作,需要添加以下代碼,對應位置可在mian函數找到

/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */
  /* USER CODE BEGIN 2 */
	eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,從站地址爲0x01,端口爲1。
	eMBEnable(  );//使能modbus
  /* USER CODE END 2 */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		( void )eMBPoll(  );//啓動modbus偵聽
  }
  /* USER CODE END 3 */

至此修改完畢,編譯下載之後即可使用modbus poll進行連接測試。

21.modbus測試

將上述代碼編譯下載到板子,用TTL轉USB接入PC,找到在PC的對應端口即可打開ModbusPoll進行通信測試
代碼下載成功後打開ModbusPoll,打開讀寫定義並設置爲從站地址1,功能04讀輸入寄存器,起始地址0,長度爲4,如圖所示
在這裏插入圖片描述
讀寫設置
按F3進行連接,連接設置如圖,串口所在位置會顯示TTL轉串口的芯片型號,按照如下設定後確定。
在這裏插入圖片描述
即可得到下圖,由於我們輸入寄存器存放的是16進制數,所以要將ModbusPoll顯示模式改爲16進制才能顯示相同內容
在這裏插入圖片描述
在這裏插入圖片描述
最終效果如下圖,ModbusPoll讀取的值與STM32內寄存器內的值一致,讀取成功!
在這裏插入圖片描述

至此,freeModbusRTU移植成功!,具體代碼已上傳,詳見我的碼雲F7_ModbusRTU

做完事情就要去打一下游戲!
希望大家不要白piao,點贊評論打賞素質三連!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章