一:平台工具资源介绍
二:使用CubeMX创建基础工程
三:添加FreeModbus软件包到工程
四:适配FreeModbus
五:测试
前言
这是基于STM32从站的FreeModbus-rtu移植,使用hal库。
一:软硬件工具资源介绍
1、CubeMX6.4.0
2、FreeeModbus源码1.5.0版本
3、STM32G474控制板
4、keil5.36
二:使用CubeMX创建基础工程
1、配置串口
2、配置定时器
三:添加FreeModbus软件包到工程
1、源码文件介绍,并提取使用到的文件
新建一个名为STM32MB的文件夹。然后打开freeModbus代码包的demo文件夹,之后将BARE文件夹内所有内容复制到STM32MB文件夹下,复制完成如图
回到freeModbus代码包,复制整个modbus文件夹也粘贴到STM32MB文件夹内,完成效果如图
将STM32MB文件夹移动到stm32cubeMX生成的工程目录下,如图
2、在keil新建FreeModbus分支文件夹 FreeModbus_Port 、FreeModbus_Source并从源码添加文件
打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,MB内添加STM32MB文件夹下modbus文件夹内所有c文件以及根目录的demo.c文件,MB_Port内添加STM32MB文件夹下port文件夹内所有c文件,如图所示
四:适配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、