FreeModbus RTU传输

首先,在使能modbus协议栈的时候,会调用pvMBFrameStartCur函数

/* 使能modbus */
eMBErrorCode eMBEnable(void)
{
	eMBErrorCode eStatus = MB_ENOERR;

	/* modbus还未使能 */
	if(eMBState == STATE_DISABLED)
	{
		/* 启动modbus */
		pvMBFrameStartCur();
		/* 设置modbus状态为使能 */
		eMBState = STATE_ENABLED;
	}
	else
	{
		/* 状态不合法 */
		eStatus = MB_EILLSTATE;
	}

	return eStatus;
}

 在rtu模式下pvMBFrameStartCur指针指向eMBRTUStart函数

/* 启动modbus rtu */
void eMBRTUStart(void)
{
	ENTER_CRITICAL_SECTION();

	/* 接收状态设置为接收初始化 */
	eRcvState = STATE_RX_INIT;

	/* 串口打开接收、关闭发送 */
	vMBPortSerialEnable(TRUE, FALSE);

	/* 打开超时定时器 */
	vMBPortTimersEnable();

	EXIT_CRITICAL_SECTION();
}

启动RTU时,接收状态eRcvState 设置为接收初始化态STATE_RX_INIT。同时打开接收中断,并开启超时定时器。

情况分为两种,在超时之前接收到数据,在超时之间没有接收到错误。

如果在超时之前接收到数据。直接将数据丢弃,并重新开始定时。直到超时,超时标识一帧数据传输完毕,然后进行数据处理。

在超时之间没有接收到错误,向主程序发送就绪事件,关闭超时定时器,将接收状态eRcvState设置为接收空闲态STATE_RX_IDLE

/* modbus rtu超时函数 */
BOOL xMBRTUTimerT35Expired(void)
{
	BOOL xNeedPoll = FALSE;

	/* 判断接收状态 */
	switch(eRcvState)
	{
		/* 接收初始化 */
		case STATE_RX_INIT:
			/* 发送就绪事件 */
			xNeedPoll = xMBPortEventPost(EV_READY);
			break;

		......
	}

	/* 关闭超时定时器 */
	vMBPortTimersDisable();
	/* 将接收状态设置为接收空闲 */
	eRcvState = STATE_RX_IDLE;

	return xNeedPoll;
}

主程序接收到就绪事件后什么也没做 

/* modbus轮询 */
eMBErrorCode eMBPoll(void)
{
	......

	/* 获取事件 */
	if(xMBPortEventGet(&eEvent) == TRUE)
	{
		/* 判断事件类型 */
		switch(eEvent)
		{
			/* 就绪事件 */
			case EV_READY:
				break;

			......
		}
	}

	return MB_ENOERR;
}

在接收空闲态下,如果接收到数据,将接收状态eRcvState切换为接收态STATE_RX_RCV,并将数据存入RTU数据缓冲区。打开超时定时器。

/* modbus rtu接收一个字节函数 */
BOOL xMBRTUReceiveFSM(void)
{
	BOOL xTaskNeedSwitch = FALSE;
	UCHAR ucByte;

	assert_param(eSndState == STATE_TX_IDLE);

	/* 串口接收一个字节 */
	(void)xMBPortSerialGetByte((CHAR *)&ucByte);

	/* 判断接收状态 */
	switch(eRcvState)
	{
		......

		/* 接收空闲状态 */
		case STATE_RX_IDLE:
			/* 接收缓冲区偏移量初始化为0 */
			usRcvBufferPos = 0;
			/* 将接收到的数据放到接收缓冲区中,偏移量加一 */
			ucRTUBuf[usRcvBufferPos++] = ucByte;
			/* 将接收状态切换为接收态 */
			eRcvState = STATE_RX_RCV;
			/* 超时定时器使能 */
			vMBPortTimersEnable();
			break;

		......
	}

	return xTaskNeedSwitch;
}

在接收态下继续接收数据

/* modbus rtu接收一个字节函数 */
BOOL xMBRTUReceiveFSM(void)
{
	BOOL xTaskNeedSwitch = FALSE;
	UCHAR ucByte;

	assert_param(eSndState == STATE_TX_IDLE);

	/* 串口接收一个字节 */
	(void)xMBPortSerialGetByte((CHAR *)&ucByte);

	/* 判断接收状态 */
	switch(eRcvState)
	{
		......

		/* 接收态 */
		case STATE_RX_RCV:
			/* RTU数据帧最大256字节 */
			if(usRcvBufferPos < MB_SER_PDU_SIZE_MAX)
			{
				/* 将接收到的数据放到接收缓冲区中,偏移量加一 */
				ucRTUBuf[usRcvBufferPos++] = ucByte;
			}
			/* 接收字节数超过256字节 */
			else
			{
				/* 接收错误状态 */
				eRcvState = STATE_RX_ERROR;
			}
			/* 超时定时器使能 */
			vMBPortTimersEnable();
			break;
	}

	return xTaskNeedSwitch;
}

直到数据接收完毕,产生超时。向主程序发送接收完成事件,关闭超时定时器,将接收状态eRcvState切换为接收空闲状态STATE_RX_IDLE。

/* modbus rtu超时函数 */
BOOL xMBRTUTimerT35Expired(void)
{
	BOOL xNeedPoll = FALSE;

	/* 判断接收状态 */
	switch(eRcvState)
	{
		......

		/* 接收态 */
		case STATE_RX_RCV:
			/* 发送接收完成事件 */
			xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED);
			break;

		......
	}

	/* 关闭超时定时器 */
	vMBPortTimersDisable();
	/* 将接收状态设置为接收空闲 */
	eRcvState = STATE_RX_IDLE;

	return xNeedPoll;
}

主程序接收到接收完成事件之后,对数据帧进行校验和拆解,最后会得到PDU数据的指针和长度。并向主程序发送执行事件。

/* modbus轮询 */
eMBErrorCode eMBPoll(void)
{
	......

	/* 获取事件 */
	if(xMBPortEventGet(&eEvent) == TRUE)
	{
		/* 判断事件类型 */
		switch(eEvent)
		{
			......

			/* 接收完成事件 */
			case EV_FRAME_RECEIVED:
				/* modbus接收函数,获取地址、PDU指针、PDU长度 */
				eStatus = peMBFrameReceiveCur(&ucRcvAddress, &ucMBFrame, &usLength);
				if(eStatus == MB_ENOERR)
				{
					/* 判断地址是否吻合 */
					if((ucRcvAddress == ucMBAddress) || (ucRcvAddress == MB_ADDRESS_BROADCAST))
					{
						/* 发送执行事件 */
						(void)xMBPortEventPost(EV_EXECUTE);
					}
				}
				break;

			......
		}
	}

	return MB_ENOERR;
}

下面看一下peMBFrameReceiveCur调用的eMBRTUReceive函数。主要工作是,对数据帧进行CRC校验,然后对数据帧进行拆分。

/* modbus rtu接收函数 */
eMBErrorCode eMBRTUReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength)
{
	BOOL xFrameReceived = FALSE;
	eMBErrorCode eStatus = MB_ENOERR;

	ENTER_CRITICAL_SECTION();
	assert_param(usRcvBufferPos < MB_SER_PDU_SIZE_MAX);

	/* RTU数据帧最小4字节,进行CRC校验 */
	if((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) && 
		 (usMBCRC16((UCHAR *)ucRTUBuf, usRcvBufferPos) == 0))
	{
		/* 从机地址 */
		*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];
		/* PDU长度=ADU长度-1字节(地址)-2字节(CRC) */
		*pusLength = (USHORT)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);
		/* PDU数据指针 */
		*pucFrame = (UCHAR *)&ucRTUBuf[MB_SER_PDU_PDU_OFF];
		/* 已经接收到数据 */
		xFrameReceived = TRUE;
	}
	/* 检验失败 */
	else
	{
		/* IO错误 */
		eStatus = MB_EIO;
	}

	EXIT_CRITICAL_SECTION();

	return eStatus;
}

主程序接收到执行事件之后,判断功能码,调用相应功能函数。然后对主机进行响应。

/* modbus轮询 */
eMBErrorCode eMBPoll(void)
{
	......

	/* 获取事件 */
	if(xMBPortEventGet(&eEvent) == TRUE)
	{
		/* 判断事件类型 */
		switch(eEvent)
		{
			......

			/* 执行事件 */
			case EV_EXECUTE:
				/* 功能码 */
				ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
				
				eException = MB_EX_ILLEGAL_FUNCTION;

				/* 遍历所有支持的功能码 */
				for(i = 0; i < MB_FUNC_HANDLERS_MAX; i++)
				{
					/* 遍历完了 */
					if(xFuncHandlers[i].ucFunctionCode == 0)
					{
						break;
					}
					/* 匹配到合适的功能码 */
					else if(xFuncHandlers[i].ucFunctionCode == ucFunctionCode)
					{
						/* 调用相关功能 */
						eException = xFuncHandlers[i].pxHandler(ucMBFrame, &usLength);
						break;
					}
				}

				/* 不是广播 */
				if(ucRcvAddress != MB_ADDRESS_BROADCAST)
				{
					/* 出现异常 */
					if(eException != MB_EX_NONE)
					{
						/* PDU长度初始化为0 */
						usLength = 0;
						/* 功能码+0x80则表示异常 */
						ucMBFrame[usLength++] = (UCHAR)(ucFunctionCode | MB_FUNC_ERROR);
						/* 异常码 */
						ucMBFrame[usLength++] = eException;
					}
					
					if((eMBCurrentMode == MB_ASCII) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS)
					{
						vMBPortTimersDelay(MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS);
					}    

					/* 发送响应帧 */
					eStatus = peMBFrameSendCur(ucMBAddress, ucMBFrame, usLength);
				}
				break;

			......
		}
	}

	return MB_ENOERR;
}


peMBFrameSendCur指针调用eMBRTUSend对主机进行响应。主要工作包括:将PDU封装为ADU数据,将发送状态eSndState切换为发送态STATE_TX_XMIT,并启动发送,发送一个字节。

/* modbus rtu发送函数 */
eMBErrorCode eMBRTUSend(UCHAR ucSlaveAddress, const UCHAR *pucFrame, USHORT usLength)
{
	eMBErrorCode eStatus = MB_ENOERR;
	USHORT usCRC16;

	ENTER_CRITICAL_SECTION();

	/* 接收空闲状态 */
	if(eRcvState == STATE_RX_IDLE)
	{
		/* 将指针偏移到ADU */
		pucSndBufferCur = (UCHAR *)pucFrame - 1;
		/* 1字节(从机地址) */
		usSndBufferCount = 1;
		/* 从机地址 */
		pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
		
		/* PDU长度 */
		usSndBufferCount += usLength;
		
		/* CRC */
		usCRC16 = usMBCRC16((UCHAR *)pucSndBufferCur, usSndBufferCount);
		ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 & 0xFF);
		ucRTUBuf[usSndBufferCount++] = (UCHAR)(usCRC16 >> 8);

		/* 发送状态 */
		eSndState = STATE_TX_XMIT;
		/* 串口启动,使能发送 */
		vMBPortSerialEnable(FALSE, TRUE);
	}
	/* 接收不在空闲状态不能发送 */
	else
	{
		/* IO错误 */
		eStatus = MB_EIO;
	}

	EXIT_CRITICAL_SECTION();

	return eStatus;
}

发送完一个字节之后,产生发送中断,调用pxMBFrameCBTransmitterEmpty,pxMBFrameCBTransmitterEmpty指针指向xMBRTUTransmitFSM函数。将数据一个字节,一个字节发送出去。直到发送完毕,通知主程序发送完成事件,并将发送状态eSndState切换为发送空闲态STATE_TX_IDLE。

/* modbus rtu发送一个字节函数 */
BOOL xMBRTUTransmitFSM(void)
{
	BOOL xNeedPoll = FALSE;

	assert_param(eRcvState == STATE_RX_IDLE);

	/* 判断发送状态 */
	switch(eSndState)
	{
		/* 发送空闲状态 */
		case STATE_TX_IDLE:
			/* 串口接收启动、发送关闭 */
			vMBPortSerialEnable(TRUE, FALSE);
			break;

		/* 发送状态 */
		case STATE_TX_XMIT:
			/* 还有数据未发送 */
			if(usSndBufferCount != 0)
			{
				/* 串口发送一个字节 */
				xMBPortSerialPutByte((CHAR)*pucSndBufferCur);
				/* 将指针向后偏移一个 */
				pucSndBufferCur++;
				/* 剩余字节数减一 */
				usSndBufferCount--;
			}
			/* 数据发完 */
			else
			{
				/* 发送发送完成事件 */
				xNeedPoll = xMBPortEventPost(EV_FRAME_SENT);
				/* 串口接收启动、发送关闭 */
				vMBPortSerialEnable(TRUE, FALSE);
				/* 发送状态设置为空闲状态 */
				eSndState = STATE_TX_IDLE;
			}
			break;
	}

	return xNeedPoll;
}

主程序接收到发送完成事件后,什么也不做

/* modbus轮询 */
eMBErrorCode eMBPoll(void)
{
	......

	/* 获取事件 */
	if(xMBPortEventGet(&eEvent) == TRUE)
	{
		/* 判断事件类型 */
		switch(eEvent)
		{
			......

			/* 发送完成 */
			case EV_FRAME_SENT:
				break;
		}
	}

	return MB_ENOERR;
}
发布了208 篇原创文章 · 获赞 90 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/lushoumin/article/details/89083236