freemodbus移植讲解
2016-10-21 20:30阅读:
一
为什么要移植Freemodbus
为什么要移植
Freemodbus,这个问题需要从两个方面来回答。第一,
modbus是一个非常好的应用层协议,它很简洁也相对完善。对于还没有接触过
modbus的朋友来说
,我非常不建议直接移植freemodbus,应该耐心的从modbus文档入手,并充分把握身边的所有资源,例如PLC的中modbus部分。第二,其实嵌入式系统的通信协议可以自己制定,但是通过实践发现自己定制的协议漏洞百出,尤其是扩展极为困难。我始终认为借鉴他人的经验是很好的途径。借鉴他人成熟的代码,可以减少调试的时间,实现的功能也多了不少。
个人观点,仅供参考。
freemodbus小提示
freemodbus只能使用从机功能。freemodbus更适合嵌入式系统,虽然例子中也有WIN32的例子,如果想要做PC机程序并实现主机功能,推荐使用另一个modbus库——NMODBUS,使用C#开发。同样WINFORM也可以通过自己编写串口代码实现modbus功能,但是这会花费很长的时间,可能是一周也可能是一个月,如果使用现成的代码库,那么开发时间可能只有10分钟。
自己整理的modbus协议
MODBUS
协议整理.pdf
代码参考了这个帖子,感谢你的分享。
点击这里
二
freeemodbus中如何通过串口发送和接收数据
freemodbus通过串口中断的方式接收和发送数据。采用这种做法我想可以节省程序等待的时间,并且也短充分使用CPU的资源。串口中断接收毋庸置疑,在中断服务函数中把数据保存在数组中,以便稍后处理。但是串口发送中断使用哪种形式?串口发送中断至少有两种方式,第一种,数据寄存器空中断,只要数据寄存器为空并且中断屏蔽位置位,那么中断就会发生;第二种,发送完成中断,若数据寄存器的数据发送完成并且中断屏蔽位置位,那么中断也会发送。我非常建议各位使用串口发送完成中断。freemodbus多使用RS485通信中,从机要么接收要么发送,多数情况下从机处于接收状态,要有数据发送时才进入发送状态。进入发送状态时,数据被一个一个字节发送出去,当最后一个字节被发送出去之后,从机再次进入接收状态。如果使用发送寄存器为空中断,还需要使用其他的方法才可以判断最后一个字节的数据是否发送完成。如果使用数据寄存器为空中断,那么将很有可能丢失最后一个字节。(马潮老师的AVR图书中也推荐使用发送完成中断,交流性质的文章,就没有参考文献了。)
三
freemodbus中如何判断帧结束
大家应该清楚,modbus协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内,没有接收到新的字符数据,那么就认为收到了新的帧。接下来就可以处理数据了,首当其冲的就是判断帧的合法性。Modbus通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。
四 整体代码
下面给出一个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
0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS
8
这两个宏定义,决定了保持寄存器的起始地址和总个数。需要强调的是,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;
- }
最后,如果收到一个有效的数据帧,那么就可以开始处理了。
第一步,判断寄存器的地址是否在合法的范围内。
if( ( (int16_t)usAddress >= REG_HOLDING_START )
\
&& ( usAddress + usNRegs
<= REG_HOLDING_START + REG_HOLDING_NREGS ) )
第二步,判断需要操作寄存器的偏移地址。
给个例子可以迅速的说明问题,例如访问寄存器的起始地址为0x0002,保持寄存器的起始地址为0x0000,那么这个访问的偏移量为2,程序就从保持寄存器数组的第2个(从0开始)开始操作。
第三步,读写操作分开处理
case MB_REG_READ:
while( usNRegs >
0 )
{
*pucRegBuffer++ = (
uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = (
uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
以读操作为例,代码不多说了,请大家注意操作的顺序。保持寄存器以16位形式保存,但是modbus通信时以字节为单位,高位字节数据在前,低位数据字节在后。
四 串口相关部分代码编写
串口部分的代码编写比较常规,主要有三个函数,串口初始化,串口数据发送和串口数据接收。除了以上三个函数之外,还有串口中断服务函数。
- 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;