之前都是其他工程师把通讯部分做好,我只管用,最近花了点时间,详细把freemodbus的代码看了一遍,写个读书笔记。有点乱,以后再整理吧
从机接收01 03 00 00 00 01 84 0A
从机发送 01 03 02 14 7B F7 67
pvMBFrameStartCur = eMBRTUStart;
pvMBFrameStopCur = eMBRTUStop;
peMBFrameSendCur = eMBRTUSend;
peMBFrameReceiveCur = eMBRTUReceive;
pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL;
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM;
pxMBPortCBTimerExpired = xMBRTUTimerT35Expired;
整个流程:
全局变量
eMBRcvState 接收状态
eMBSndState 发送状态
初始化:
eMBInit(MB_RTU, 0x01, 0x01,115200, MB_PAR_NONE);
{
初始化3个方面: 1、串口初始化 2、定时器初始化 3、事件队列初始化
}
eMBEnable(); //启动FreeModbus
{
pvMBFrameStartCur( );-----> eMBRTUStart( void ) 函数指针调用 {eRcvState = STATE_RX_INIT;允许接收,关闭发送,开启定时器}
}
//大循环里事件查询,处理数据 数据使用中断接收,有接收状态机和发送状态机(xMBRTUReceiveFSM)(xMBRTUTransmitFSM)
eMBPoll( );
////////////////////////////////////////////////
从机中断接收主机发来的数据
注意:eMBEnable(),使能定时器后,会产生一次定时器中断,在定时器中断里执行 xMBRTUTimerT35Expired();
初始化后 eRcvState = STATE_RX_INIT;case STATE_RX_INIT:xNeedPoll = xMBPortEventPost( EV_READY ); 发送接收准备好的事件
vMBPortTimersDisable( ); //关闭定时器
eRcvState = STATE_RX_IDLE;//接收处于空闲状态
********此时,所有准备工作完毕,等待主机发来数据,触发串口接收中断*************
从机接收数据流程:
中断接收
触发接收中断 prvvUARTRxISR( ); --》 pxMBFrameCBByteReceived( );---》最终调用xMBRTUReceiveFSM( void )(接收状态机)接收数据
xMBRTUReceiveFSM( void )(接收状态机) 详解
函数中定义:UCHAR ucByte;(用来临时保存接收到的数据)
1:( void )xMBPortSerialGetByte( ( CHAR * ) & ucByte );( *pucByte = USART_ReceiveData(USART2);)//接收到的数据放入 UCHAR ucByte;
switch (eRcvState):
//进入STATE_RX_IDLE 把接收到的数据放入ucRTUBuf[]数组,切换状态eRcvState = STATE_RX_RCV ,开启定时器
2:接收状态机 :第一次进入中断:(eRcvState = STATE_RX_IDLE ) case STATE_RX_IDLE: usRcvBufferPos = 0; ucRTUBuf[usRcvBufferPos++] = ucByte;eRcvState = STATE_RX_RCV;vMBPortTimersEnable( ); //开启定时器
第二次进入中断:(eRcvState = STATE_RX_RCV ) case STATE_RX_RCV: if( usRcvBufferPos < MB_SER_PDU_SIZE_MAX )
一直进入这一状态机持续接收数据,一直到接受完一帧数据 {
ucRTUBuf[usRcvBufferPos++] = ucByte; //一直接收
}
else
{
eRcvState = STATE_RX_ERROR; //接收完成
}
vMBPortTimersEnable( ); //定时器重启
ucRTUBuf[MB_SER_PDU_SIZE_MAX]; 接收的数据存到这个数组中 全局的
接收后的数据存储详细地址
从机接收01 03 00 00 00 01 84 0A
ucRTUBuf[0] = 01 设备地址
ucRTUBuf[1] = 03 功能码
ucRTUBuf[2] = 00 读保持寄存器的起始地址 高8位
ucRTUBuf[3] = 00 读保持寄存器的起始地址 低8位
ucRTUBuf[4] = 00 读保持寄存器的个数 高8位
ucRTUBuf[5] = 01 读保持寄存器的个数 低8位
ucRTUBuf[6] = 84 CRC 高8位
ucRTUBuf[7] = 0A CRC 低8位
3:没有数据可以接收,不再进入接收状态机,所以定时器不会重启,此时等待产生定时器中断 表明数据接收完成
prvvTIMERExpiredISR( ); --》 pxMBPortCBTimerExpired---xMBRTUTimerT35Expired
case STATE_RX_RCV:
xMBPortEventPost( EV_FRAME_RECEIVED ); //发送事件,接收完成事件
vMBPortTimersDisable( ); //关定时器
eRcvState = STATE_RX_IDLE; //接收空闲 可以再次接收
4: eMBPoll(); 事件查询 大循环中一直在查询事件
static UCHAR *ucMBFrame;
static UCHAR ucRcvAddress;
static UCHAR ucFunctionCode;
static USHORT usLength;
static eMBException eException;
xMBPortEventGet( &eEvent ) //获取事件
switch ( eEvent )
{
case EV_READY:
break;
case EV_FRAME_RECEIVED: //接收到一个数据包
eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength ) ---》 进行数据长度和CRC校验(返回pucRcvAddress 设备地址)如果我的数据发送事件( void )xMBPortEventPost( EV_EXECUTE );
//功能码地址(用到一个C语言技巧)unsigned char *p,a[10]={0},p = a; *(p+i) = p[i]
if( eStatus == MB_ENOERR )
{
/* Check if the frame is for us. If not ignore the frame. */
if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )(设备地址)如果我的数据发送事件
{
( void )xMBPortEventPost( EV_EXECUTE );发送事件
}
}
break;
}
case EV_EXECUTE://对数据包处理
ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF]; 获取功能码
eException = MB_EX_ILLEGAL_FUNCTION; 0x01
for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ )
{
/* No more function handlers registered. Abort. */
if( xFuncHandlers[i].ucFunctionCode == 0 )
{
break;
}
else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
{ //找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
//找到了进行处理 如果是06命令 eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
break;
}
}
详细解读
case EV_FRAME_RECEIVED: //接收到一个数据包
{
1:eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength ); 函数指针
最终执行函数 eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{ 在这个函数中
1:对数据长度和数据包CRC校验 if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
2:数据填充
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF]; *pucRcvAddress = ucRTUBuf[0]; ucRcvAddress 保存了 ucRTUBuf[0] 即设备地址 0x01
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );//减去CRC两位和头
usLength = 8-1-2 = 5
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF]; //从全局数组中获取功能码地址
*pucFrame = ( UCHAR * ) & ucRTUBuf[1]; // ucMBFrame中放的就是功能吗的地址
}
2:校验地址,看看数据是不是发给本设备的
if( eStatus == MB_ENOERR )
{
/* Check if the frame is for us. If not ignore the frame. */
if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) )//校验地址 地址是否是自己的地址或广播地址
{
( void )xMBPortEventPost( EV_EXECUTE ); //发送事件
}
}
}
case EV_EXECUTE: //确认接收到的数据包是自己的,进行处理
{
ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];
(这里使用了一个C语言技巧,)可以这样理解 int * p;int a[10]; p = &a[x] ; *(p+i) = p[i] = a[x+i]
{
static UCHAR *ucMBFrame;
在eMBRTUReceive函数中
ucMBFrame = ( UCHAR * ) & ucRTUBuf[1]; //从全局数组中获取功能码地址 MB_SER_PDU_PDU_OFF = 1 MB_PDU_FUNC_OFF = 0
因为此时 ucMBFrame =( UCHAR * &ucRTUBuf[1],所以 ucFunctionCode = ucMBFrame[0] 等价 ucFunctionCode = ucRTUBuf[1];---> ucFunctionCode = 0x03
}
for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ) //遍历数组
{
/* No more function handlers registered. Abort. */ //查找是否有对应的功能处理函数
if( xFuncHandlers[i].ucFunctionCode == 0 )
{
break;
} //找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode )
{ //找到了进行处理 如果是06命令 eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
break;
}
if( ucRcvAddress != MB_ADDRESS_BROADCAST )
{
if( eException != MB_EX_NONE )
{
/* An exception occured. Build an error frame. */
usLength = 0;
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 ); //回调函数处理完后,进入发送,先发送一个字节 ,然后进入发送状态机
}
}
}
详解:
eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );
根据我的数据,进行0x03命令处理
//找到了进行处理 如果是03命令 就是eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
调用这个函数进行处理
eMBException
eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen )
{
USHORT usRegAddress;
USHORT usRegCount;
UCHAR *pucFrameCur;
eMBException eStatus = MB_EX_NONE;
eMBErrorCode eRegStatus;
if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) ) //4+1 = 5
{ //因为pucFrame = ( UCHAR * ) & ucRTUBuf[1] pucFrame[1] = ucRTUBuf[2] //读保持寄存器的起始地址 高8位
usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); //同样使用指针技巧 *(p+i)=p[i] 上面已经说了
usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); //获取一个16位的地址 读保持寄存器的地址
//usRegAddress 就是接收到数据ucRTUBuf[2]、ucRTUBuf[3] 合成的地址数据
usRegAddress++; //ws add 不知道为什么++
usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 );//获取要读取保持寄存器的个数
usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );
//usRegCount 读取保持寄存器的个数 ucRTUBuf[4] 、ucRTUBuf[5] 合成的地址数据
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/ //读取个数校验
if( ( usRegCount >= 1 ) && ( usRegCount <= MB_PDU_FUNC_READ_REGCNT_MAX ) )
{
/* Set the current PDU data pointer to the beginning. */
pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; // pucFrameCur = &ucRTUBuf[1]
*usLen = MB_PDU_FUNC_OFF; //*usLen = 0; 从这里开始要往 ucRTUBuf[] 缓存区填充数据,发送和接收共用一个缓存区
/* First byte contains the function code. */
*pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER; //ucRTUBuf[1] = MB_FUNC_READ_HOLDING_REGISTER = 3 回复的数据时 地址+功能码+发送数据的个数-+数据区----
*usLen += 1; //数据长度加1
/* Second byte in the response contain the number of bytes. */
*pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 ); //ucRTUBuf[2] = 要发送读保持寄存器中数据的个数 保持寄存器是16进制的,usRegCount * 2 转成 UCHAR 发送的个数
*usLen += 1;//数据长度加1
/* Make callback to fill the buffer.回调函数进一步填充发送的数据 需要用户自己实现 把要保持寄存器中的数据,复制到发送ucRTUBuf[]内填充 */
eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); //下面详细解释这个函数,需要用户自己写
/* If an error occured convert it into a Modbus exception. */
if( eRegStatus != MB_ENOERR )
{
eStatus = prveMBError2Exception( eRegStatus );
}
else
{
*usLen += usRegCount * 2;
}
}
else
{
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
}
else
{
/* Can't be a valid request because the length is incorrect. */
eStatus = MB_EX_ILLEGAL_DATA_VALUE;
}
return eStatus;
}
//发送时回调函数,把保持寄存器的内容填充到ucRTUBuf[]内,发送缓存区
eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); //下面详细解释这个函数,需要用户自己写
#define REG_HOLDING_START 0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS 8
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]
= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode ) //参数解释 pucRegBuffer缓存区指针当前是ucRTUBuf[3]
{ // usAddress 从接收缓存区提取的读保持寄存的起始地址
eMBErrorCode eStatus = MB_ENOERR; // usRegCount从接收缓存区提取的要读取的保持寄存器的个数
USHORT iRegIndex; // MB_REG_READ 功能码,读
USHORT * pusRegHoldingBuf;
USHORT REG_HOLDING_START1;
USHORT REG_HOLDING_NREGS1;
USHORT usRegHoldStart;
pusRegHoldingBuf = usRegHoldingBuf; //用户自己定义的
REG_HOLDING_START1 = REG_HOLDING_START;//用户自己定义的
REG_HOLDING_NREGS1 = REG_HOLDING_NREGS; //用户自己定义的
usRegHoldStart = REG_HOLDING_START;//用户自己定义的
usAddress--;//FreeModbus功能函数中已经加1,为保证与缓冲区首地址一致,故减1 看代码知道
if( ( usAddress >= REG_HOLDING_START1 ) &&
( usAddress + usNRegs <= REG_HOLDING_START1 + REG_HOLDING_NREGS1 ) ) //地址校验
{
iRegIndex = usAddress - usRegHoldStart; //起始偏移量 在该例子中 usRegHoldStart = 0x0000,就是pusRegHoldingBuf[0],假如从0x0003开始读,iRegIndex = 3,pusRegHoldingBuf[3],
switch ( eMode ) //用户可以根据自己的需要自由修改
{
// Pass current register values to the protocol stack.
case MB_REG_READ:
while( usNRegs > 0 )
{ //填充数据
*pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( pusRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
// Update current register values with new values from the protocol stack.
case MB_REG_WRITE:
while( usNRegs > 0 )
{
pusRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
pusRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
处理完,数据缓存区后,开始发送数据
开始发送数据 恢复数据包
peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );---》eMBRTUSend (eSndState = STATE_TX_XMIT;)
装载头地址 和 CRC
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
usSndBufferCount += usLength;
就是把 ucRTUBuf[0] = ucSlaveAddress,数据长度再加1
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
把CRC填充到ucRTUBuf[]的数据尾部
eSndState = STATE_TX_XMIT;
//插入代码 启动第一次发送,这样才可以进入发送完成中断
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;
usSndBufferCount--;
vMBPortSerialEnable( FALSE, TRUE );//切换到允许发送完成中断,然后等待发送完成中断的到来,开启发送状态机,发送数据
主动发送一次 等待进入发送完成中断(TC)
void USART2_IRQHandler(void)
{
//发生完成中断
if(USART_GetITStatus(USART2, USART_IT_TC) == SET)
{
prvvUARTTxReadyISR( );
//清除中断标志
USART_ClearITPendingBit(USART2, USART_IT_TC);
}
}
prvvUARTTxReadyISR( ); -- > pxMBFrameCBTransmitterEmpty( );(发送状态机)
xMBRTUTransmitFSM();
case STATE_TX_XMIT:
/* check if we are finished. */
if( usSndBufferCount != 0 ) //一直发送数据 知道数据发送完成
{
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++; /* next byte in sendbuffer. */
usSndBufferCount--;
}
else
{
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT ); //发送完成后 发送事件
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
vMBPortSerialEnable( TRUE, FALSE ); //允许接收数据 ,停止发送数据
eSndState = STATE_TX_IDLE; //发送空闲
}
break;
}
此时 eMBPoll() case EV_FRAME_SENT:
break;
所以从初始化到第一次接收到数据和第一次回复数据 ,完成一次通信
下次通讯 时,先产生串口接收中断
第一次完成的通讯结束。
一周热门 更多>