Modbus数据帧
也就是串口在静默了
T3.5秒以后收到的第一个字节作为一个
Modbus
数据帧的首字节,然
后收到的字符间隔只要不超出
T3.5的时间间隔,都作为这一帧数据来处理。
当串口在>
T3.5
没有收到数据时,则认为当前主站
Modbus
发送完了一个数据帧。
可以进行后面的
Modbus
数据帧处理了。
再收到的数据回作为下一个
Modbus
数据帧的首字节。
接收一个字节,是由串口中断来进行完成的,判断是否超时是由定时器中断来完成的。
因此
以上看是很清楚的一个协议流程需要串口和定时器配合共同完成。
所以在看RTU实现协议
时一定要记住这点。
从站主要是接收,被动响应,所以先分析接收机的状态:
接收状态机
接收状态包含以下几种:
STATE_RX_INIT,/*!
<接收机在初始态*/
STATE_RX_IDLE,/*!
<接收机在空闲态*/
STATE_RX_RCV,/*!
<接收到一帧数据*/
STATE_RX_ERROR/*!
<错误数据帧*/
状态机转换图:
串口收到一个字节
eMBEnable()
重启T3.5开始下一个
T3.5延迟
发送一个EV_READY
事件
eMBRTUStart()
设置初态
T3.5超时
T3.5
超时
STATE_RX_INIT
STATE_RX_IDLE
启动T3.5定时
T3.5超时
串口收到一
个字节
发送一个EV_FRAME_RECEIVED
事件
STATE_RX_RCV
串口收到一个新字
节,且总字节数不
超过最大帧长
串口收到一个新字节,但总字节数超过最大帧长
STATE_RX_ERROR
串口收到一个新字节
状态机的转换不是由一个函数来确定的,是由多个函数共同作用的结果。
主要要T3.5的定
时中断和串口的接收中断。
所以在程序一开始要对串口和定时器进行正确的设置。
当初始化完毕后,接收状态机初始态是STATE_RX_INIT,同时T3.5定时器启动。
如果在T3.5
定时到来之前,串口收到了一个字节,则认为是还没准备好的情况下,总线发起的一次
Modbus传输,此时这帧数据是不处理的,所以,要等到这一帧数据传送完(T3.5超时)。
当发生T3.5超时事件时,说明总线上的数据帧已经传完,处于空闲状态,为发起下一帧数
据传输做好了准备。
所以接收状态机转换为STATE_RX_IDLE,同时发送一个EV_READY事件,
告诉Modbus以及准备好接收数据了。
同时关闭T3.5定时。
在STATE_RX_IDLE状态下,只会接收串口数据来触发状态转换(定时被关闭)。
当接收到一
个字节时,认为总线开始了一个新的数据帧的传输,把这个字节保存在Modbus数据缓冲区,并将缓冲区指针加1,并将接收状态机转换为STATE_RX_RCV。
在STATE_RX_RCV状态下,接收到一个新字节,将该字节保存在缓冲区,并将缓冲区指针加
1,判断接收的字节总长是否>最大帧长,如果不大于,继续保持STATE_RX_RCV状态,等待
接收下一个字节或超时(接收完成)。
当大于最大帧长,说明接收到了错误帧。
状态转到
STATE_RX_ERROR。
在STATE_RX_RCV状态下,产生了一个T3.5超时,此时认为总线已经发送完成一个Modbus
数据帧,将状态转换到STATE_RX_IDLE,等待下一个数据帧到来。
同时触发EV_FRAME_RECEIVED事件,告诉ModbusPoll已经成功接收一帧数据,可以解析并处理了。
在STATE_RX_ERROR状态下,接收到再多的字节都认为是错误的,所以一直停留在错误态,直到来了一个T3.5超时事件,此时认为总线上错误的数据帧发送完毕,总线静默,为下一
个数据帧准备好!
接收状态机转换为STATE_RX_IDLE。
Modbus的初始化流程:
eMBRTUInit()
调用
eMBInit()
ModBus初始化:
调用
调用
1、设置从机号
eQueuedEvent为空
2、设置eMode(RTU)
eMBState=
3、设置波特率
STATE_DISABLED
Uart_Init()
xMBPortTimersInit()
4、设置T35定时器
ModBus使能:
eMBState=
1、置端口为接收状态
STATE_ENABLED
2、开端口定时功能
eRcvState=
此时串口中断开,定时中断开
STATE_RX_INIT;
以下是FreeModbus的各个功能函数解析:
1、//-----初始化Modbus协议------------------------
eMBInit(MB_RTU,Slave_Adress,0,115200,MB_PAR_NONE);
eMBInit()
状态码=无错误
地址号是否有效
是
否
状态码=非法参数
MB_RTU
eMBRTUInit
返回状态码
设备号=地址号
eMode=?
MB_ASCII
eMBASCIIInit
返回状态码
否
状态码=无错误
是
xMBPortEventInit
正确
eMBState=
STATE_DISABLED
default
状态码=非法参数
错误
状态码
=MB_EPORTERR
返回状态码
eMBRTUInit(UCHARucSlaveAddress,UCHARucPort,ULONGulBaudRate,eMBParityeParity)
eMBRTUInit()
调用库函数对串口初始化
uart_init(ulBaudRate);
否
usTimerT35_50us
=更长些
根据超时时间
初始化定时器
3.5个字符时间区分不同的帧,即接收到的两个字符之间时间间隔小于3.5个字符时间时认为是同一个帧的,如果间隔大于3.5个字符时间则认为是不同帧的,在一般的串口通信中,发送1个字符需要:
1位起始位,8位数据位,1位校验位(可无),1位停止位,总共1+8+1+1=11位,3.5个字符时间
就是3.5*11=38.5位,假如波特率是9600,那么传输1位的时间是1000/9600=0.10416667(ms),这
样,3.5个字符时间就大约是4ms,即定时器需要的中断时间
状态码=无错误
xMBPortSerialInit()
否
初始化是否成功
是
状态码
根据波特率设
定超时时间
=MB_EPORTERR
波特率>19200
是
usTimerT35_50us
=35
xMBPortTimersInit()
否
初始化是否成功
是
状态码
=MB_EPORTERR
返回状态码
至此,Modbus的初始化工作完成。
串口中断被打开,超时定时器配置好。
eMBEnable()
该函数指针指向
eMBRTUStart
eMBRTUStart
eMBEnable()
eStatus状态码=无错误
初始化时
的状态否
eMBState==
STATE_DISABLED
是
pvMBFrameStartCur()
eMBState=
STATE_ENABLED
返回eStatus状态码
eMBRTUStart()
eStatus=
MB_EILLSTATE
置接收状态位初
始化状态
置端口为接收状态
开端口定时功能
eRcvState=
STATE_RX_INIT;
vMBPortSerialEnable(TRUE,FALSE);
在端口使能函数中
vMBPortTimersEnable();
设置485的收发状态
返回
当串口接收到数据时发生中断,由串口中断程序处理USART1_IRQHandler()
USART1_IRQHandler
()
是
接收事件?
否
调用prvvUARTRxISR()
处理
是
发送准备好事件?
否
调用
调用prvvUARTTxReadyISR()
处理
pxMBFrameCBByteReceived();
处理
在RTU模式,该函数指针指向
xMBRTUReceiveFSM()
调用
实际调用的是
返回
pxMBFrameCBTransmitterEmpty();
xMBRTUTransmitFSM()
处理
在RTU模式,该函数指针指向
xMBRTUTransmitFSM
()
实际调用的是xMBRTUTransmitFSM()
实际是调用函数:
USART_ReceiveDa
ta(USART1);
STATE_RX_INIT
vMBPortTimersEnable();
xMBRTUReceiveFSM()
确认当前发送为空闲状态
调用
xMBPortSerialGetByte()
接收一个字节数据
判断当前接收状态
eRcvState
STATE_RX_ERRORSTATE_RX_IDLESTATE_RX_RCV
vMBPortTimersEnable();
接收第一个字节,并将状
将当前接收字节加入到接收缓冲
态置为STATE_RX_RCV
区,当接收字节数大于最大帧
同时,启动T3.5定时器
长,将放弃该帧(eRcvState=
STATE_RX_ERROR)
每收到一个字节复位T3.5定时器
返回
xMBRTUTransmitFSM()
确认当前接收为空闲状态
判断当前接收状态
eSndState
STATE_TX_IDLE
vMBPortSerialEnable(TRUE,FALSE);
由发变收
产生一个发送完毕事件
xNeedPoll=xMBPortEventPost(
EV_FRAME_SENT);
vMBPortSerialEnable(TRUE,FALSE);
由发变收
STATE_TX_XMIT
否
发送缓冲区不为零
是
发送当前字节
xMBPortSerialPutByte((CHAR
)*pucSndBufferCur)
实际是调用函数:
USART_SendData(USART1,ucByte);
更新缓冲器指针和大小
返回
TIM2_IRQHandler
中断函数调用
调用
TIMERExpiredISR(void)
调用
pxMBPortCBTimerExpired()
RTU模式下,该函数指针指向
xMBRTUTimerT35Expired()
判断当前接收状态
eRcvState
STATE_RX_INIT
STATE_RX_ERROR
STATE_RX_RCV
Default
置返回值为错误态
产生一个准备好事件
xMBPortEventPost(
产生一个接收一帧事件
确认各参数正常?
EV_READY);xMBPortEventPost(
EV_FRAME_RECEIVED)
vMBPortTimersDisable();
eRcvState=STATE_RX_IDLE;
返回
Modbus的主查询函数eMBPoll()
eMBPoll()
状态码=无错误
否
ModBus是否使能协议栈出错,并返回错误码
是
否
EV_READY
获取当前MB_EVENT成功
是
判断当前EVENT
EV_FRAME_SENT
EV_FRAME_RECEIVEDEV_EXECUTE
peMBFrameReceiveCur(
//从接收缓冲取出功能码
&ucRcvAddress,
&ucMBFrame,&usLength
Break;
);//查询接收结果,并将结
依次查询各个功能码
Break;果保存到ucMBFrame
否
否
查询接收结果无错误
本机支持该功能码
是
是
判断当前数据帧是本机
否
调用该功能码处理函数
数据或是广播数据
是
派送一个执行事件
否
xMBPortEventPost(
地址不等于广播地址
EV_EXECUTE);
是
按照MB协议,发送应答帧
返回