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;
}
【FreeModbus RTU传输】
推荐阅读
- 网络夺命连环问系列|网络夺命连环问5--HTTP怎么传输大文件()
- javaEmail一(传输协议简介)
- 关于ajax异步分页传输数据到页面为字符串的JS解决办法
- FTP创建
- Virtual|Virtual DOM(虚拟DOM)
- FastAPI传输响应文件
- Github上文件传输/修改/删除的新手教程
- TCP|TCP 协议如何保证可靠传输
- netty4.x学习一netty数据传输(Hello|netty4.x学习一netty数据传输(Hello World)
- Android程序性能优化全面解析,(含内存优化,耗电优化,apk大小优化,网络传输与数据存储优化等实战解析)