freemodbus移植讲解

标签:
it |
分类: 数据库 |
一
二
三
四
下面给出一个STM32平台上使用FREEMODBUS最简单的例子,操作保持寄存器,此时操作指令可以为03,06和16;
- #include "stm32f10x.h"
- #include
- #include "mb.h"
- #include "mbutils.h"
- //保持寄存器起始地址
- #define REG_HOLDING_START 0x0000
- //保持寄存器数量
- #define REG_HOLDING_NREGS 8
- //保持寄存器内容
- uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]
- = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
- int main(void)
- {
- //初始化 RTU模式 从机地址为1 USART1 9600 无校验
- eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
- //启动FreeModbus
- eMBEnable();
- while (1)
- {
- //FreeMODBUS不断查询
- eMBPoll();
- }
- }
- eMBErrorCode
- eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
- eMBRegisterMode eMode )
- {
- //错误状态
- eMBErrorCode eStatus = MB_ENOERR;
- //偏移量
- int16_t iRegIndex;
- //判断寄存器是不是在范围内
- if( ( (int16_t)usAddress >= REG_HOLDING_START ) \
- && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
- {
- //计算偏移量
- iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);
- switch ( eMode )
- {
- //读处理函数
- case MB_REG_READ:
- while( usNRegs > 0 )
- {
- *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );
- *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
- iRegIndex++;
- usNRegs--;
- }
- break;
- //写处理函数
- case MB_REG_WRITE:
- while( usNRegs > 0 )
- {
- usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
- usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
- iRegIndex++;
- usNRegs--;
- }
- break;
- }
- }
- else
- {
- //返回错误状态
- eStatus = MB_ENOREG;
- }
- return eStatus;
- }
复制代码
先给大家一个整体的印象,先让大家会使用FREEMODBUS,再详细描述细节
//保持寄存器起始地址
#define REG_HOLDING_START
//保持寄存器数量
#define REG_HOLDING_NREGS
这两个宏定义,决定了保持寄存器的起始地址和总个数。需要强调的是,modbus寄存器的地址有两套规则,一套称为PLC地址,为5位十进制数,例如40001。另一套是协议地址,PLC地址40001意味着该参数类型为保持寄存器,协议地址为0x0000,这里面有对应关系,去掉PLC地址的最高位,然后剩下的减1即可。这会存在一个问题,PLC地址30002和PLC地址40002的协议地址同为0x0001,此时访问时是不是会冲突呢。亲们,当然不会了,30001为输入寄存器,需要使用04指令访问,而40001为保持寄存器,可以使用03、06和16指令访问。所以,用好modbus还是要熟悉协议本生,切不可着急。
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]
= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
接下来定义了保持寄存器的内容,在这里请大家注意了,保持寄存器为无符号16位数据。在测试的情况下,我随便找了一些数据进行测试。看数据的本质似乎看不出说明规律,但是usRegHoldingBuf却是以16进制保存了浮点数。
- int main(void)
- {
- //初始化 RTU模式 从机地址为1 USART1 9600 无校验
- eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
- //启动FreeModbus
- eMBEnable();
- while (1)
- {
- //FreeMODBUS不断查询
- eMBPoll();
- }
- }
接下来就进入主函数部分。有三个FREEMODBUS提供的函数,eMBInit,eMBEnable和eMBPoll。eMBInit为modbus的初始化函数,eMBEnable为modbus的使能函数,而eMBPoll为modbus的查询函数,eMBPoll也是非常单纯的函数,查询是否有数据帧到达,如果有数据到达,便进行相依的处理。再次观察这几个函数,只有eMBInit有很多的参数,这些参数和位于系统底层的硬件有关,这个应该引起移植过程的更多关注。下面几个章节再议。
- eMBErrorCode
- eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
- eMBRegisterMode eMode )
- {
- //错误状态
- eMBErrorCode eStatus = MB_ENOERR;
- //偏移量
- int16_t iRegIndex;
- //判断寄存器是不是在范围内
- if( ( (int16_t)usAddress >= REG_HOLDING_START ) \
- && ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
- {
- //计算偏移量
- iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);
- switch ( eMode )
- {
- //读处理函数
- case MB_REG_READ:
- while( usNRegs > 0 )
- {
- *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );
- *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
- iRegIndex++;
- usNRegs--;
- }
- break;
- //写处理函数
- case MB_REG_WRITE:
- while( usNRegs > 0 )
- {
- usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
- usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
- iRegIndex++;
- usNRegs--;
- }
- break;
- }
- }
- else
- {
- //返回错误状态
- eStatus = MB_ENOREG;
- }
- return eStatus;
- }
最后,如果收到一个有效的数据帧,那么就可以开始处理了。
第一步,判断寄存器的地址是否在合法的范围内。
第二步,判断需要操作寄存器的偏移地址。
第三步,读写操作分开处理
四
- BOOL
- xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
- {
- (void)ucPORT; //不修改串口
- (void)ucDataBits; //不修改数据位长度
- (void)eParity; //不修改校验格式
- GPIO_InitTypeDef GPIO_InitStructure;
- USART_InitTypeDef USART_InitStructure;
- //使能USART1,GPIOA
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
- RCC_APB2Periph_USART1, ENABLE);
- //GPIOA9 USART1_Tx
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- //GPIOA.10 USART1_Rx
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮动输入
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- USART_InitStructure.USART_BaudRate = ulBaudRate; //只修改波特率
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;
- USART_InitStructure.USART_StopBits = USART_StopBits_1;
- USART_InitStructure.USART_Parity = USART_Parity_No;
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
- //串口初始化
- USART_Init(USART1, &USART_InitStructure);
- //使能USART1
- USART_Cmd(USART1, ENABLE);
- NVIC_InitTypeDef NVIC_InitStructure;
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
- //设定USART1 中断优先级
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPrio
rity = 0; - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
- NVIC_Init(&NVIC_InitStructure);
- //最后配置485发送和接收模式
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
- //GPIOD.8
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOD, &GPIO_InitStructure);
- return TRUE;
- }
传入的参数有端口号,波特率,数据位和校验位,可以根据实际的情况修改代码。在这里我并没有修改其他参数,至于传入的波特率是有效的。除了配置串口的相关参数之外,还需要配置串口的中断优先级。最后,由于使用485模式,还需要一个发送接收控制端,该IO配置为推挽输出模式。
- void
- vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
- {
- if(xRxEnable)
- {
- //使能接收和接收中断
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
- //MAX485操作 低电平为接收模式
- GPIO_ResetBits(GPIOD,GPIO_Pin_8);
- }
- else
- {
- USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
- //MAX485操作 高电平为发送模式
- GPIO_SetBits(GPIOD,GPIO_Pin_8);
- }
- if(xTxEnable)
- {
- //使能发送完成中断
- USART_ITConfig(USART1, USART_IT_TC, ENABLE);
- }
- else
- {
- //禁止发送完成中断
- USART_ITConfig(USART1, USART_IT_TC, DISABLE);
- }
- }
由于485使用半双工模式,从机一般处于接收状态,有数据发送时才会进入发送模式。在FreeModbus中有专门的控制接收和发送状态的函数,在这里不但可以打开或关闭接收和发送中断,还可以控制485收发芯片的发送接收端口。代码非常简单,但是还是建议各位使用发送完成中断。
- BOOL
- xMBPortSerialPutByte( CHAR ucByte )
- {
- //发送数据
- USART_SendData(USART1, ucByte);
- return TRUE;
- }
- BOOL
- xMBPortSerialGetByte( CHAR * pucByte )
- {
- //接收数据
- *pucByte = USART_ReceiveData(USART1);
- return TRUE;
- }
- xMBPortSerialPutByte和xMBPortSerialGetByte两个函数用于串口发送和接收数据,在这里只要调用STM32的库函数即可。
- static void prvvUARTTxReadyISR( void )
- {
- //mb.c eMBInit函数中
- //pxMBFrameCBTransmitterEm
pty = xMBRTUTransmitFSM - //发送状态机
- pxMBFrameCBTransmitterEm
pty(); - }
- static void prvvUARTRxISR( void )
- {
- //mb.c eMBInit函数中
- //pxMBFrameCBByteReceived = xMBRTUReceiveFSM
- //接收状态机
- pxMBFrameCBByteReceived();
- }
- void USART1_IRQHandler(void)
- {
- //发生接收中断
- if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
- {
- prvvUARTRxISR();
- //清除中断标志位
- USART_ClearITPendingBit(USART1, USART_IT_RXNE);
- }
- //发生完成中断
- if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
- {
- prvvUARTTxReadyISR();
- //清除中断标志
- USART_ClearITPendingBit(USART1, USART_IT_TC);
- }
- }
若进入串口中断服务函数,则要调用FreeModbus中响应的函数,串口接收中断服务函数对应prvvUARTRxISR(),其代码如下
- static void prvvUARTRxISR( void )
- {
- //mb.c eMBInit函数中
- //pxMBFrameCBByteReceived = xMBRTUReceiveFSM
- //接收状态机
- pxMBFrameCBByteReceived();
- }
复制代码
在prvvUARTRxISR中又调用了pxMBFrameCBByteReceived(),其实pxMBFrameCBTransmitterEm
BOOL( *pxMBFrameCBTransmitterEm
在mb.c文件的eMBInit函数完成赋值。一般情况下都会选择RTU模式,那么pxMBFrameCBByteReceived就和xMBRTUReceiveFSM等价了,
pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
同理,若发生串口发送完成中断,该中断服务函数对应prvvUARTTxReadyISR,其代码如下
- static void prvvUARTTxReadyISR( void )
- {
- //mb.c eMBInit函数中
- //pxMBFrameCBTransmitterEm
pty = xMBRTUTransmitFSM - //发送状态机
- pxMBFrameCBTransmitterEm
pty(); - }
在prvvUARTTxReadyISR中又调用了pxMBFrameCBTransmitterEm
- pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
- usSndBufferCount = 1;
- pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
- usSndBufferCount += usLength;
- usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
- ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
- ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );
- //发送状态转换,在中断中不断发送
- eSndState = STATE_TX_XMIT;
- //插入代码 启动第一次发送,这样才可以进入发送完成中断
- xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
- pucSndBufferCur++;
- usSndBufferCount--;
- //使能发送状态,禁止接收状态
- vMBPortSerialEnable( FALSE, TRUE );
写到这里给位可能看的不是很明白,建议研究一下FreeModbus的源码