困惑了将近一年多的ModbusRTU在我昨天穷极无聊给自己定目标的情况下搞出来了,以前移植不出来主要原因就是基本功不扎实,没有进一步理解串口和定时器配置的原理,一通操作,移植完之后就Timeout,接下来就分享一下我是怎么从0开始移植这个协议的。
项目已上传码云,文章底部有链接!
操作之前先讲两个主要问题
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来。
ModbusRTU是通过定时器和串口配合来实现Modbus通信的,所以定时器是决定有没有超时的一大关键问题,由串口设置部分可知,定时器设置是要配合串口设置的波特率食用比较香,所以根据我使用的115200波特率可以得到我定时器设置。首先是APB1的主频率获取到,modbus要求通过预分配后得到的周期为50us,对应频率为20KHz。根据rtu初始化代码得到自动重载值设置为35。
以下为正式修改Modbus代码,上面比较繁琐,熟悉stm32cubemx的老司机可以直接从15步看起
我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注
#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( );
}
我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注
#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_SET_COUNTER(&htim4,0);
__HAL_TIM_ENABLE(&htim4);
}
inline void
vMBPortTimersDisable( ) //取消定时器中断
{
/* Disable any pending timers. */
__HAL_TIM_DISABLE(&htim4);
__HAL_TIM_SET_COUNTER(&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( );
}
位置在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"
打开工程内的中断文件,是在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的配置
硬件接口方面结束之后就可以开始写功能了,在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;
}
启动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进行连接测试。
将上述代码编译下载到板子,用TTL转USB接入PC,找到在PC的对应端口即可打开ModbusPoll进行通信测试
代码下载成功后打开ModbusPoll,打开读写定义并设置为从站地址1,功能04读输入寄存器,起始地址0,长度为4,如图所示
按F3进行连接,连接设置如图,串口所在位置会显示TTL转串口的芯片型号,按照如下设定后确定。
即可得到下图,由于我们输入寄存器存放的是16进制数,所以要将ModbusPoll显示模式改为16进制才能显示相同内容
最终效果如下图,ModbusPoll读取的值与STM32内寄存器内的值一致,读取成功!
至此,freeModbusRTU移植成功!,具体代码已上传,详见我的码云F7_ModbusRTU
做完事情就要去打一下游戏!
希望大家不要白piao,点赞评论打赏素质三连!!!