FreeModbus版本:1.6
在mb.c文件中
先看一下静态变量的定义
/* ----------------------- Static variables ---------------------------------*/
static UCHAR ucMBAddress;
static eMBMode eMBCurrentMode;
ucMBAddress是从机地址,eMBCurrentMode是Modbus 的工作模式,如下:
typedef enum
{
MB_RTU, /*!< RTU transmission mode. */
MB_ASCII, /*!< ASCII transmission mode. */
MB_TCP /*!< TCP mode. */
} eMBMode;
我这里只使用RTU模式,在include/mbconfig.h文件中有一些宏定义,可以配置打开,我这里只打开了RTU使能。
eMBState 代表Modbus的工作状态,定义后赋值为STATE_NOT_INITIALIZED未初始化状态。
static enum
{
STATE_ENABLED,
STATE_DISABLED,
STATE_NOT_INITIALIZED
} eMBState = STATE_NOT_INITIALIZED;
静态变量定义下面还有一些静态函数指针变量
/* Functions pointer which are initialized in eMBInit( ). Depending on the
* mode (RTU or ASCII) the are set to the correct implementations.
*/
static peMBFrameSend peMBFrameSendCur;
static pvMBFrameStart pvMBFrameStartCur;
static pvMBFrameStop pvMBFrameStopCur;
static peMBFrameReceive peMBFrameReceiveCur;
static pvMBFrameClose pvMBFrameCloseCur;
简单点讲,以static peMBFrameSend peMBFrameSendCur;为例,可以理解为定义了一个变量peMBFrameSendCur,而它的类型是peMBFrameSend 。
那么它是什么呢,是函数指针类型Functions pointer。
也就是说peMBFrameSendCur可以指向一个函数,可以把一个函数赋值给它(参数和返回值要保持一致)。
函数原型定义在mbframe.h文件中
/* ----------------------- Prototypes 0-------------------------------------*/
typedef void ( *pvMBFrameStart ) ( void );
typedef void ( *pvMBFrameStop ) ( void );
typedef eMBErrorCode( *peMBFrameReceive ) ( UCHAR * pucRcvAddress,
UCHAR ** pucFrame,
USHORT * pusLength );
typedef eMBErrorCode( *peMBFrameSend ) ( UCHAR slaveAddress,
const UCHAR * pucFrame,
USHORT usLength );
typedef void( *pvMBFrameClose ) ( void );
在mb.c文件中还定义了几个回调函数指针,这里只用到了前三个,分别是字节接收,发送空,定时器溢出回调函数。
刚开始时我一直不理解这个名字pxMBFrameCBByteReceived 该怎么个意思。
MB是Modbus,Frame是帧,CB是回调函数CallBack。
/* Callback functions required by the porting layer. They are called when
* an external event has happend which includes a timeout or the reception
* or transmission of a character.
*/
BOOL( *pxMBFrameCBByteReceived ) ( void );
BOOL( *pxMBFrameCBTransmitterEmpty ) ( void );
BOOL( *pxMBPortCBTimerExpired ) ( void );
BOOL( *pxMBFrameCBReceiveFSMCur ) ( void );
BOOL( *pxMBFrameCBTransmitFSMCur ) ( void );
下面看一下本篇主要学习的eMBInit,Modbus初始化,这里我只保留了RTU模式,其它两种模式都删除了。
/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;
/* check preconditions */
if( ( ucSlaveAddress == MB_ADDRESS_BROADCAST ) ||
( ucSlaveAddress < MB_ADDRESS_MIN ) || ( ucSlaveAddress > MB_ADDRESS_MAX ) )
{
eStatus = MB_EINVAL;
}
else
{
ucMBAddress = ucSlaveAddress;
switch ( eMode )
{
#if MB_RTU_ENABLED > 0
case MB_RTU:
pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );
break;
#endif
default:
eStatus = MB_EINVAL;
}
if( eStatus == MB_ENOERR )
{
if( !xMBPortEventInit( ) )
{
/* port dependent event module initalization failed. */
eStatus = MB_EPORTERR;
}
else
{
eMBCurrentMode = eMode;
eMBState = STATE_DISABLED;
}
}
}
return eStatus;
}
先看一下参数
eMBMode eMode, 工作模式,这里选择RTU
UCHAR ucSlaveAddress, 从机地址,UCHAR 类型,即unsigned char类型
UCHAR ucPort, 端口(这个不重要)
ULONG ulBaudRate, 波特率
eMBParity eParity,校验位:奇校验,偶校验,无校验
返回值为错误码,,错误码被协议栈的所有函数使用,这里初始化为无错误
/*! \ingroup modbus
* \brief Errorcodes used by all function in the protocol stack.
*/
typedef enum
{
MB_ENOERR, /*!< no error. */
MB_ENOREG, /*!< illegal register address. */
MB_EINVAL, /*!< illegal argument. */
MB_EPORTERR, /*!< porting layer error. */
MB_ENORES, /*!< insufficient resources. */
MB_EIO, /*!< I/O error. */
MB_EILLSTATE, /*!< protocol stack in illegal state. */
MB_ETIMEDOUT /*!< timeout error occurred. */
} eMBErrorCode;
先检查传进来的从机地址是否符合要求,
① 不是广播地址0
② 满足在1到147之间
满足要求,则将这个从机地址赋值给我们的静态变量ucMBAddress,从此我们的从机地址就确定了。
然后将前面提到的函数指针都给赋值(不赋值都为空),这些函数都定义在mbrtc.c文件中
然后调用函数eStatus = eMBRTUInit( ucMBAddress, ucPort, ulBaudRate, eParity );进行RTU初始化。
/* ----------------------- Start implementation -----------------------------*/
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; /* 1800us. */
}
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;
}
在调用xMBPortSerialInit进行串口初始化,这个函数在portserial.c文件中,这个函数是留给用户自己实现的,就是初始化函数,实际上串口初始化放在外面自己初始化也可以,反正只要初始化了就行。比如使用cubeMX生成的MX_USART2_UART_Init();
下一步是初始化定时器。
因为T3.5字符原则嘛,就是两帧之间间隔3.5个字符的时间长度,字符时间长度当然跟波特率有关系啦。
If baudrate > 19200 时给固定时间间隔 t35 = 1750us.
当If baudrate ≤ 19200us时TimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
这个公式怎么来的呢
令波特率为Baud ,传输一位要的时间为: bitTime = 1000 / Baud ms
传输1个字符,要传11位: charTime = 11 * bitTime = 11 * ( 1000 / Baud ) ms
那么传输 3.5 字符: T3.5 = 3.5 * charTime = 3.5 * 11 * ( 1000 / Baud ) ms
换成整数:T3.5 = ( 7 * 11 * 10^6 ) / ( 2 * Baud ) us
50us的个数:Period = T3.5 / 50 = ( 7 * 220000 ) / ( 2 * Baud )
定时器初始化,我这里使用的是STM32H743,TIM 240MHz,
所以设置Prescaler = 11999;,这样一个时基就是12000/240000000 = 1/20000 = 1/20ms = 1000/20 us = 50us。
Period = usTim1Timerout50us - 1;
所以自动重装载值设为 50us 的个数即可。
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim7.Instance = TIM7;
htim7.Init.Prescaler = 11999;
htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
htim7.Init.Period = usTim1Timerout50us - 1;
htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim7) != HAL_OK)
{
return FALSE;
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
我们继续回到eMBInit函数
eMBRTUInit初始化完成之后,调用xMBPortEventInit初始化,然后把当前模式赋值为RTU,协议栈状态由未初始化变未使能。
eStatus为本函数内状态,其值为返回的错误码。
eMBState为协议栈状态,其值为:未初始化,未使能,使能。
二者不要搞混。
好了初始化结束。
流程:
eMBInit →
赋值从机地址 →
赋值函数指针 →
eMBRTUInit(→xMBPortSerialInit →xMBPortTimersInit→) →
xMBPortEventInit →
eMBState = STATE_DISABLED;