FreeModbus开源协议简介

个人笔记,供个人查阅。

目录

FreeModbus软硬件需求

物理层接口

portserial.c

porttimer.c

应用层回调

主函数

运行流程

功能码使用

Function Code:01,读线圈输出

Function Code:02,读离散输入

Function Code:03,读保持寄存器

Function Code:04,读输入寄存器

Function Code:05,写单个线圈

Function Code:06,写单个寄存器

Function Code:0F,写多个线圈

Function Code:10,写多个寄存器


FreeModbus是奥地利人写的Modbus协议,主要针对嵌入式应用的免费通用Modbus协议的移植。modbus通信协议栈包括两层:应用层协议(定义了数据模型和功能)和网络层。

FreeModbus提供了RTU/ASCII传输模式和TCP协议支持,遵循BSD许可证(意味着用户可以将FreeModbus应用于商业环境中)。

FreeModbus软硬件需求

FreeModbus协议对硬件的需求非常少,基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM微控制器都足够了。

拥有一个异步串行接口,能够支持接收缓冲区满和发送缓冲区空中断。

拥有一个能够产生RTU传输所需要的T3.5字符超时定时器的时钟。

对于软件部分,仅仅需要一个简单的事件队列。

物理层接口

在物理层,用户只需完成串行接口和T3.5字符超时定时器的配置即可。具体应修改portserial.c和porttimer.c。

portserial.c

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    if(xRxEnable == TRUE)
        HAL_UART_Receive_IT(&huart1, (uint8_t *)&rxBuffer, 1);
    else
        HAL_UART_AbortReceive_IT(&huart1);
    
    if(xTxEnable == TRUE)
        prvvUARTTxReadyISR();
}

函数功能:设置串口状态。

当xRxEnable为真时,应使能串口接收和接收中断。在RS485通讯系统中,还要注意将RS485接口芯片设置为接收使能状态。

当xTxEnable为真时,应使能串口发送和发送中断。在RS485通讯系统中,还要注意将RS485接口芯片设置为发送使能状态。

void vMBPortClose( void )
{
    HAL_UART_DeInit(&huart1);
}

函数功能:关闭modbus通讯端口。具体的,应在此函数中关闭通讯接口的发送使能及接收使能。

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity);

函数功能:初始化串口通讯接口。

若使用RTU模式,则ucDataBits=8;若使用ASCII模式,则ucDataBits=7。

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    txBuffer = ucByte;
    HAL_UART_Transmit_IT(&huart1, (uint8_t *)&txBuffer, 1);
    return TRUE;
}

函数功能:通讯端口发送一字节数据。

注意,由于使用的是中断发送,故只需要将数据放到发送寄存器即可。

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    *pucByte = rxBuffer;
    return TRUE;
}

函数功能:通讯端口接收一字节数据。

注意,由于使用的是中断接收,故只需要将接收寄存器的值放到*pucByte即可。

void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

函数功能:发送中断函数。此函数无需修改。只需在用户的发送中断函数中调用此函数即可。同时,用户应该在调用此函数后,清除发送中断标志位。

void prvvUARTRxISR( void )
{
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&rxBuffer, 1);
    pxMBFrameCBByteReceived(  );
}

函数功能:接收中断函数。此函数无需修改。只需在用户的接收中断函数中调用此函数即可。同时,用户应该在调用此函数后,清除接收中断标志位。

porttimer.c

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    htim7.Init.Period = usTim1Timerout50us - 1;
    
    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;
}

函数功能:初始化超时定时器。usTim1Timerout50us为50us的个数。

用户应根据所使用的硬件初始化超时定时器,使之能产生中断时间为usTim1Timerout50us*50us的中断。 

inline void
vMBPortTimersEnable(  )
{
    __HAL_TIM_CLEAR_IT(&htim7, TIM_IT_UPDATE);
	__HAL_TIM_SetCounter(&htim7, 0);//这里一定要清零计数器
	HAL_TIM_Base_Start_IT(&htim7);
}

函数功能:使能超时定时器。用户需要在此函数中清除中断标志位、清零定时器计数值,并重新使能定时器中断。

inline void
vMBPortTimersDisable(  )
{
    HAL_TIM_Base_Stop_IT(&htim7);
}

函数功能:关闭超时定时器。用户需要在此函数中清零定时器计数值,并关闭定时器中断。

void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

函数功能:定时器中断函数。此函数无需修改。只需在用户的定时器中断函数中调用此函数即可。同时,用户应该在调用此函数后,清除定时器中断标志位。

应用层回调

在应用层,用户需要定义所需要使用的寄存器,并修改对应的回调函数。回调函数有如下几个:

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs );
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode );
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode );
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete );

主函数

用户只需要在主函数中调用协议初始化代码和消息处理函数即可。

eMBErrorCode 
eMBInit( eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity, eMBStopBit eStopBit, eMBDataBit eDataBit );

协议初始化函数。

eMBErrorCode eMBSetSlaveID( UCHAR ucSlaveID, BOOL xIsRunning,
               UCHAR const *pucAdditional, USHORT usAdditionalLen );

从机ID设置函数。注意,ID表示的是设备的类型,不同于ucSlaveAddress(从机地址)。对同一通讯系统中,可以有相同的ucSlaveID,但不可以有相同的ucSlaveAddress。ucSlaveID为一字节的设备ID号;xIsRunning为设备的运行状态,0xFF为运行,0x00为停止;* pucAdditional为设备的附加描述,根据需要添加;usAdditionalLen为附加描述的长度(按字节计算)。此函数不是必须调用的。但当一个Modbus通讯系统中有不同种设备时,应调用此函数添加对应设备的描述。

eMBErrorCode eMBPoll( void );

轮训事件查询处理函数。用户需要在主循环中调用此函数。对于使用操作系统的程序,应单独创建一个任务,使操作系统能周期调用此函数。

运行流程

FreeModbus是基于消息队列的协议。协议通过检测相应的消息来完成相应功能。协议栈的初始化和运行流程如下:

首先调用mb.c的eMBInit()完成物理层设备的初始化,主要包括:串口初始化(设定波特率、数据位、校验方式)和定时器初始化(设定T35定时所需要的定时器常数)。

调用mbfuncother.c的eMBSetSlaveID()指定设备ID。(非必需流程)

调用mb.c的eMBEnable()使能协议栈,主要包括:pvMBFrameStartCur协议栈开始,将eRcvState设为STATE_RX_INIT状态,调用vMBPortSerialEnable()使能接收,调用vMBPortTimersEnable()使能超时定时器。经过T35时间后,发生第一次超时中断,在中断中,向协议栈发送消息EV_READY,并调用vMBPortTimersDisable()关闭超时定时器,同时将eRcvState设为STATE_RX_IDLE。此时,协议栈可以接收串口数据。注意,此处首先启用一次超时定时器是因为初始化完成时,串口有可能已经有数据,因为无法判断第一个数据请求的开始,故等待T35,接收下一帧请求。

此时,主函数调用eMBPoll()检测事件。(当串口接收到数据后重复以下内容)

若发生串口接收中断,且eRcvState为STATE_RX_IDLE(上面已将eRcvState设为STATE_RX_IDLE),则向接收缓存中存入接收到的字符,同时将eRcvState设为STATE_RX_RCV状态,并清零超时定时器。在下一个数据到来时,不断将数据存入接收缓存,并清零超时定时器。

如果没有接收完成,则不可能发生超时中断。发生超时中断,说明T35时间内未收到新的串口数据,根据Modbus协议的规定,这指示着一帧请求数据接收完成。在中断中,向协议栈发生消息EV_FRAME_RECEIVED,等待协议栈处理此消息。

主函数调用eMBPoll()检测到事件EV_FRAME_RECEIVED后,调用peMBFrameReceiveCur()作简单判断请求帧数据,并向协议栈发送消息EV_EXECUTE。

主函数调用eMBPoll()检测到事件EV_EXECUTE后,根据相应的请求代码查找处理该功能的函数指针来处理该功能。若不是广播消息,则调用peMBFrameSendCur()发送回复消息,在此函数中,只是把要回复的数据复制到了串口缓存中,同时将eSndState设为STATE_TX_XMIT,并通过调用vMBPortSerialEnable()使能发送中断。注意,发送中断使能后,由于串口发送寄存器本来就是空的,故在使能后将进入发送中断中。

在发送中断中,且eSndState为STATE_TX_XMIT(上面已将eSndState设为STATE_TX_XMIT),则将串口缓存中的数据发送出去,同时不断对发送字符个数统计。当发送完成后,向协议栈发送消息EV_FRAME_SENT。

主函数调用eMBPoll()检测到事件EV_FRAME_SENT后,不处理此消息。

功能码使用

Function Code:01,读线圈输出

发送1从机地址1功能码2起始地址2线圈数量2CRC
返回1从机地址1功能码1字节数1线圈状态2CRC

主机发送:01 01 00 00 00 01 FD CA

从机返回:01 01 01 00 51 88

Function Code:02,读离散输入

发送1从机地址1功能码2起始地址2线圈数量2CRC
返回1从机地址1功能码1字节数1状态2CRC

主机发送:01 02 00 00 00 01 B9 CA

从机返回:01 02 01 00 A1 88

Function Code:03,读保持寄存器

发送1从机地址1功能码2起始地址2线圈数量2CRC
返回1从机地址1功能码1字节数n寄存器值2CRC

主机发送:01 03 00 00 00 01 84 0A

从机返回:01 03 02 00 00 B8 44

Function Code:04,读输入寄存器

发送1从机地址1功能码2起始地址2线圈数量2CRC
返回1从机地址1功能码1字节数n寄存器值2CRC

主机发送:01 04 00 00 00 01 31 CA

从机返回:01 04 02 00 00 B9 30

Function Code:05,写单个线圈

线圈状态:0xFF00-ON,0x0000-OFF

发送1从机地址1功能码2起始地址2线圈状态2CRC
返回1从机地址1功能码2起始地址2线圈状态2CRC

主机发送:01 05 00 00 FF 00 C4 F7

从机返回:01 05 00 00 FF 00 C4 F7

Function Code:06,写单个寄存器

发送1从机地址1功能码2起始地址2寄存器数据2CRC
返回1从机地址1功能码2起始地址2寄存器数据2CRC

主机发送:01 06 00 00 00 00 89 CA

从机返回:01 06 00 00 00 00 89 CA

Function Code:0F,写多个线圈

发送1从机地址1功能码2起始地址2线圈数量1后跟的字节数n线圈状态2CRC
返回1从机地址1功能码2起始地址2线圈数量--2CRC

主机发送:01 0F 00 00 00 0A 02 0F F0 E0 8C

从机返回:01 0F 00 00 00 0A D5 CC

Function Code:10,写多个寄存器

发送1从机地址1功能码2起始地址2寄存器数量1后跟的字节数n寄存器数据2CRC
返回1从机地址1功能码2起始地址2寄存器数量--2CRC

写寄存器编号0开始的10个寄存器:0-3寄存器写1,4-9寄存器写0。

主机发送:01 10 00 00 00 0A 14 00 01 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DD 92

从机返回:01 10 00 00 00 0A 40 0E

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值