FreeModbus ASCII传输

首先,在使能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指针指向eMBASCIIStart函数

/* modbus ascii启动函数 */
void eMBASCIIStart(void)
{
	ENTER_CRITICAL_SECTION();

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

	/* 接收状态设置为接收空闲 */
	eRcvState = STATE_RX_IDLE;

	EXIT_CRITICAL_SECTION();

	/* 发送就绪事件 */
	(void)xMBPortEventPost(EV_READY);
}

启动RTU时,接收状态eRcvState 设置为接收空闲态STATE_RX_IDLE,打开接收中断,向主程序发送就绪事件。

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

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

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

			......
		}
	}

	return MB_ENOERR;
}

ASCII传输模式下,modbus报文包含明确的起始字符和结束字符

因此在接收空闲态下,如果接收到数据,先判断是否为起始字符。如果是起始字符,使能超时定时器,将接收状态eRcvState切换为接收态STATE_RX_RCV。由于ASCII模式下每个字节需要两个字符编码,所以需要来回切换高半字和低半字,一开始将字节位置初始化为高半字。

/* modbus ascii接收一个字节函数 */
BOOL xMBASCIIReceiveFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;
	UCHAR ucResult;

	assert_param(eSndState == STATE_TX_IDLE);

	/* 串口接收一个字节 */
	(void)xMBPortSerialGetByte((CHAR *) & ucByte);
	
	/* 判断接收状态 */
	switch(eRcvState)
	{
		......

		/* 接收空闲状态 */
		case STATE_RX_IDLE:
			/* 起始字符 */
			if(ucByte == ':')
			{
				/* 超时定时器使能 */
				vMBPortTimersEnable();
				/* 接收缓冲区偏移量初始化为0 */
				usRcvBufferPos = 0;
				/* 字节位置初始化高半字节 */
				eBytePos = BYTE_HIGH_NIBBLE;
				/* 将接收状态切换为接收态 */
				eRcvState = STATE_RX_RCV;
			}
			break;
	}

	return xNeedPoll;
}

在接收态下,将一个一个字节接收数据。直到接收到结束字符CR,将接收状态eRcvState切换为等待结束态STATE_RX_WAIT_EOF。

/* modbus ascii接收一个字节函数 */
BOOL xMBASCIIReceiveFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;
	UCHAR ucResult;

	assert_param(eSndState == STATE_TX_IDLE);

	/* 串口接收一个字节 */
	(void)xMBPortSerialGetByte((CHAR *) & ucByte);
	
	/* 判断接收状态 */
	switch(eRcvState)
	{
		/* 接收态 */
		case STATE_RX_RCV:
			/* 超时定时器使能 */
			vMBPortTimersEnable();
			/* 起始字符 */
			if(ucByte == ':')
			{
				/* 字节位置重新初始化高半字节 */
				eBytePos = BYTE_HIGH_NIBBLE;
				/* 接收缓冲区偏移量重新初始化为0 */
				usRcvBufferPos = 0;
			}
			/* 结束字符,CR */
			else if(ucByte == MB_ASCII_DEFAULT_CR)
			{
				/* 将接收状态设置为等待LF状态 */
				eRcvState = STATE_RX_WAIT_EOF;
			}
			/* 普通数据 */
			else
			{
				/* 将ascii码转换为数字 */
				ucResult = prvucMBCHAR2BIN(ucByte);
				/* 判断高半字节还是低半字节 */
				switch(eBytePos)
				{
					/* 高半字节 */
					case BYTE_HIGH_NIBBLE:
						/* 数据帧最大256字节 */
						if(usRcvBufferPos < MB_SER_PDU_SIZE_MAX)
						{
							/* 将数据填入高半字节 */
							ucASCIIBuf[usRcvBufferPos] = (UCHAR)(ucResult << 4);
							/* 字节位置设置为低半字节 */
							eBytePos = BYTE_LOW_NIBBLE;
							break;
						}
						/* 接收字节数超过256字节 */
						else
						{
							/* 接收状态设置为空闲态 */
							eRcvState = STATE_RX_IDLE;
							/* 超时定时器关闭 */
							vMBPortTimersDisable();
						}
						break;

					/* 低半字节 */
					case BYTE_LOW_NIBBLE:
						/* 将数据填入低半字节 */
						ucASCIIBuf[usRcvBufferPos] |= ucResult;
						/* 字节数加一 */
						usRcvBufferPos++;
						/* 字节位置设置为高半字节 */
						eBytePos = BYTE_HIGH_NIBBLE;
						break;
				}
			}
			break;


		......
	}

	return xNeedPoll;
}

在等待结束态下,如果接收到LF字符,则表示一帧数据结束。关闭超时定时器,将接收状态eRcvState切换为接收空闲态STATE_RX_IDLE,向主程序发送接收完成事件。

/* modbus ascii接收一个字节函数 */
BOOL xMBASCIIReceiveFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;
	UCHAR ucResult;

	assert_param(eSndState == STATE_TX_IDLE);

	/* 串口接收一个字节 */
	(void)xMBPortSerialGetByte((CHAR *) & ucByte);
	
	/* 判断接收状态 */
	switch(eRcvState)
	{
		.......

		/* 等待LF状态 */
		case STATE_RX_WAIT_EOF:
			/* 检查该字节是否为LF */
			if(ucByte == ucMBLFCharacter)
			{
				/* 超时定时器关闭 */
				vMBPortTimersDisable();
				/* 将接收状态设置为接收空闲状态 */
				eRcvState = STATE_RX_IDLE;
				/* 发送接收完成事件 */
				xNeedPoll = xMBPortEventPost(EV_FRAME_RECEIVED);
			}
			/* 该字节为起始字符 */
			else if(ucByte == ':')
			{
				/* 字节位置重新初始化高半字节 */
				eBytePos = BYTE_HIGH_NIBBLE;
				/* 接收缓冲区偏移量重新初始化为0 */
				usRcvBufferPos = 0;
				/* 将接收状态重新切换为接收态 */
				eRcvState = STATE_RX_RCV;
				/* 超时定时器使能 */
				vMBPortTimersEnable();
			}
			/* 该字节不是LF字符也不是LF字符 */
			else
			{
				/* 将接收状态设置为接收空闲状态 */
				eRcvState = STATE_RX_IDLE;
			}
			break;

		......
	}

	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调用的eMBASCIIReceive函数。主要工作是,对数据帧进行LRC校验,然后对数据帧进行拆分。

/* modbus ascii接收函数 */
eMBErrorCode eMBASCIIReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength)
{
	eMBErrorCode eStatus = MB_ENOERR;

	ENTER_CRITICAL_SECTION();
	assert_param(usRcvBufferPos < MB_SER_PDU_SIZE_MAX);

	/* ASCII数据帧最小3字节,进行LRC校验 */
	if((usRcvBufferPos >= MB_SER_PDU_SIZE_MIN) && (prvucMBLRC((UCHAR *)ucASCIIBuf, usRcvBufferPos) == 0))
	{
		/* 从机地址 */
		*pucRcvAddress = ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
		/* PDU长度=ADU长度-1字节(地址)-1字节(LRC) */
		*pusLength = (USHORT)(usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_LRC);
		/* PDU数据指针 */
		*pucFrame = (UCHAR *)&ucASCIIBuf[MB_SER_PDU_PDU_OFF];
	}
	/* 检验失败 */
	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指针调用eMBASCIISend对主机进行响应。主要工作包括:将PDU封装为ADU数据,将发送状态eSndState切换为发送开始态STATE_TX_START,并启动发送,发送一个字节。

/* modbus ascii发送函数 */
eMBErrorCode eMBASCIISend(UCHAR ucSlaveAddress, const UCHAR *pucFrame, USHORT usLength)
{
	eMBErrorCode eStatus = MB_ENOERR;
	UCHAR usLRC;

	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;

		/* LRC */
		usLRC = prvucMBLRC((UCHAR *)pucSndBufferCur, usSndBufferCount);
		ucASCIIBuf[usSndBufferCount++] = usLRC;

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

	EXIT_CRITICAL_SECTION();

	return eStatus;
}

在发送开始态下,发送起始字符。然后将发送状态eSndState切换为发送数据态STATE_TX_DATA,将字节位置初始化为高半字节。

/* modbus ascii发送一个字节函数 */
BOOL xMBASCIITransmitFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;

	assert_param(eRcvState == STATE_RX_IDLE);

	/* 判断发送状态 */
	switch(eSndState)
	{
		/* 发送开始状态 */
		case STATE_TX_START:
			/* 发送起始字符 */
			ucByte = ':';
			xMBPortSerialPutByte((CHAR)ucByte);
			/* 发送状态设置为发送态 */
			eSndState = STATE_TX_DATA;
			/* 字节位置初始化高半字节 */
			eBytePos = BYTE_HIGH_NIBBLE;
			break;

                ......
	}

	return xNeedPoll;
}

在发送数据态下,将数据一个字节一个字节发送出去。直到数据都发送完毕,发送结束字符CR,将发送状态eSndState设置为发送结束态STATE_TX_END。

/* modbus ascii发送一个字节函数 */
BOOL xMBASCIITransmitFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;

	assert_param(eRcvState == STATE_RX_IDLE);

	/* 判断发送状态 */
	switch(eSndState)
	{
		......

		/* 发送态 */
		case STATE_TX_DATA:
			/* 还有数据未发送 */
			if(usSndBufferCount > 0)
			{
				/* 判断字节位置 */
				switch(eBytePos)
				{
					/* 高半字节 */
					case BYTE_HIGH_NIBBLE:
						/* 将数字转换为ascii码 */
						ucByte = prvucMBBIN2CHAR((UCHAR)(*pucSndBufferCur >> 4));
						/* 将数据发送出去 */
						xMBPortSerialPutByte((CHAR)ucByte);
						/* 字节位置设置为低半字节 */
						eBytePos = BYTE_LOW_NIBBLE;
						break;

					/* 低半字节 */
					case BYTE_LOW_NIBBLE:
						/* 将数字转换为ascii码 */
						ucByte = prvucMBBIN2CHAR((UCHAR)(*pucSndBufferCur & 0x0F));
						/* 将数据发送出去 */
						xMBPortSerialPutByte((CHAR)ucByte);
						/* 将指针向后偏移一个 */
						pucSndBufferCur++;
						/* 字节位置设置为高半字节 */
						eBytePos = BYTE_HIGH_NIBBLE;
						/* 剩余字节数减一 */
						usSndBufferCount--;
						break;
				}
			}
			/* 数据发完 */
			else
			{
				/* 发送结束字符,CR */
				xMBPortSerialPutByte(MB_ASCII_DEFAULT_CR);
				/* 将发送状态设置为发送结束态 */
				eSndState = STATE_TX_END;
			}
			break;

		......
	}

	return xNeedPoll;
}

在发送结束态下,发送结束字符LR,将发送状态切换为通知态。

/* modbus ascii发送一个字节函数 */
BOOL xMBASCIITransmitFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;

	assert_param(eRcvState == STATE_RX_IDLE);

	/* 判断发送状态 */
	switch(eSndState)
	{
		......

		/* 发送结束态 */
		case STATE_TX_END:
			/* 发送结束字符,LF */
			xMBPortSerialPutByte((CHAR)ucMBLFCharacter);
			/* 发送状态设置为通知态 */
			eSndState = STATE_TX_NOTIFY;
			break;

		......
	}

	return xNeedPoll;
}

在通知态下,将发送状态eSndState切换为发送空闲态STATE_TX_IDLE,打开接收中断,通知主程序发送完毕事件

/* modbus ascii发送一个字节函数 */
BOOL xMBASCIITransmitFSM(void)
{
	BOOL xNeedPoll = FALSE;
	UCHAR ucByte;

	assert_param(eRcvState == STATE_RX_IDLE);

	/* 判断发送状态 */
	switch(eSndState)
	{
		......

		/* 通知态 */
		case STATE_TX_NOTIFY:
			/* 将发送状态设置为空闲态 */
			eSndState = STATE_TX_IDLE;
			/* 发送发送完成事件 */
			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/89085833