基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植
目录
- 一、CubeMX的配置
- 二、freeMODBUS移植
- 三、测试
移植前准备工作(不限于以下方式):
硬件
开发板
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移植工作基本完成
三、测试
烧录好代码,连接好板子,电脑打开Modbus Poll调试软件,Setup–>F8,按如下设置
Connection–>F3,按如下设置
连接成功后,如下所示,成功读取保持寄存器数据
Display下拉最后一个可打开收发面板
注意:质量不好的USB转串口工具可能会丢帧