学习 FreeModbus TCP服务器 在stm32f103上的实现

最近学习freemodbus 在stm32f103上的实现,有些心得,记录下来。
modbus rtu的实现在网上可以查到很多资料,很容易就成功了。而 modbus tcp的实现,费了一些周折,终于搞明白了。
测试用架构:stm32f103c8t6 + enc28j60 +EncEthernet + freemodbus TCP。
EncEthernet实现了arp + icmp + tcp协议。
让freemodbus支持tcp,需要修改在mbconfig.h
define MB_ASCII_ENABLED(0)
#define MB_RTU_ENABLED(0 )
#define MB_TCP_ENABLED(1 )
main函数里主要代码:
//定义modbus server 全局变量
#define REG_INPUT_START 1//初始地址为0会有问题
#define REG_INPUT_NREGS 8
static USHORTusRegInputBuf[REG_INPUT_NREGS] = {0x0102, 0x0304, 0x0506, 0x0708, 0x0910, 0x1112, 0x1314, 0x1516};
#define REG_HOLDING_START 1//初始地址为0会有问题
#define REG_HOLDING_NREGS 8
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x0102, 0x0304, 0x0506, 0x0708, 0x0910, 0x1112, 0x1314, 0x1516};
#define REG_COILS_START 1//初始地址为0会有问题
#define REG_COILS_SIZE 32
static UCHARucRegCoilsBuf[REG_COILS_SIZE / 8 + (REG_COILS_SIZE % 8 ? 1 : 0)] = {0};
#define REG_DISCRETE_START 1//初始地址为0会有问题
#define REG_DISCRETE_SIZE 32
static UCHARucRegDiscreteBuf[REG_DISCRETE_SIZE / 8 + (REG_DISCRETE_SIZE % 8 ? 1 : 0)] = {0};

#define BUFFER_SIZE 1518
unsigned char buf[BUFFER_SIZE + 1]; //定义enc28j60缓冲区,收发共用!
unsigned char mymac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x24}; //定义网卡mac地址
unsigned char myip[4] = {192, 168, 2, 8}; //定义网卡IP
unsigned char * info_addr ; //保存tcp数据的起始地址,buf数据=以太网头+IP头+TCP头+TCP数据。
extern unsigned intinfo_data_len; //tcp数据长度,这个定义在EncEthernet里定义了
unsigned int info_len; //tcp数据长度
int main()
{
unsigned int plen; //enc28j60接收数据长度
unsigned int dat_p; //TCP数据在buf里的位置
eMBErrorCodeeStatus;
enc28j60_init(mymac); //enc28j60初始化,包含了SPI初始化
init_ip_arp_udp_tcp(mymac, myip); //EncEthernet初始化,设置mac和IP地址
eStatus = eMBTCPInit(502 ); //设置freemodbus tcp 接收端口
eStatus = eMBEnable(); //启用freemodbus
while(1) {
plen = enc28j60_packet_receive(buf, BUFFER_SIZE); //从enc28j60读取数据
if(plen > 0) { //如果有数据
if(eth_type_is_arp_and_my_ip(buf, plen )) //如果是arp请求,则回应
make_arp_answer_from_request(buf);
else if(eth_type_is_ip_and_my_ip(buf, plen ) >0){ //如果包的目的ip是本机
if(buf[IP_PROTO_P] == IP_PROTO_ICMP_V && buf[ICMP_TYPE_P] == ICMP_TYPE_ECHOREQUEST_V)make_echo_reply_from_request(buf, plen ); //如果是ping包,则回应
else if (buf[IP_PROTO_P] == IP_PROTO_TCP_V && buf[TCP_DST_PORT_H_P] == (tcpport>>8) && buf[TCP_DST_PORT_L_P] == (tcpport &0xff)){ //如果是发给本机的tcp包
if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) // 如果是握手sync,则tcp握手
make_tcp_synack_from_syn(buf);
else if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){ //如果报里有ack标志
init_len_info(buf); //计算数据包的长度,存储在info_data_len
dat_p = get_tcp_data_pointer(); //获得数据位置
if (dat_p == 0) { //没有数据
if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V) //如果包有Fin标志,对方请求结束连接,则回应
make_tcp_ack_from_any(buf,0);
}else{//如果包含有ack并携带数据
info_addr=&buf[dat_p]; //存储数据首地址
info_len=info_data_len;将数据长度转储到info_len里,因为info_data_len是freemodbus定义的
//info_len=buf[IP_TOTLEN_H_P]*8 + buf[IP_TOTLEN_L_P]-IP_HEADER_LEN-4*(buf[TCP_HEADER_LEN_P]>>4); //或者这样计算info_len
xMBPortEventPost( EV_FRAME_RECEIVED ); //通知有数据到来,如果没有这一步,则eMBPoll不处理数据
( void)eMBPoll(); //数据处理
}
}
}
}



freemodbus TCP server 还需要实现几个函数

BOOLxMBTCPPortInit( USHORT usTCPPort )
{
if( usTCPPort == 0 ) return FALSE;
else return TRUE;
}
void vMBTCPPortDisable( )
{}
FreeModbusTCP 从xMBTCPPortGetRequest函数获得TCP数据,经过处理后,调用xMBTCPPortSendResponse函数,将处理结果数据作为参数传递,用户在该函数获得处理后的modbus报文,进行进一步处理。
BOOLxMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
*ppucMBTCPFrame = info_addr; //TCP数据地址
*usTCPLength = info_len; //TCP数据长度
return TRUE;
}
BOOL xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{
【学习 FreeModbus TCP服务器 在stm32f103上的实现】info_len=usTCPLength;
info_data_len=info_len;
make_tcp_ack_with_data(buf,usTCPLength); //发送数据
return TRUE;
}
此外eMBPoll函数还需小修改一下,确保一次调用eMBPoll能把数据处理完毕。
将if( xMBPortEventGet( &eEvent ) == TRUE )修改为while( xMBPortEventGet( &eEvent ) == TRUE )
实现Modbus协议,还需添加以下函数,modbus RTU和modbus TCP通用,这个是从网上抄的,感谢!
eMBErrorCodeeMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCodeeStatus = MB_ENOERR;
intiRegIndex;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - REG_INPUT_START );
while( usNRegs > 0 )
{
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
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;
//return MB_ENOREG;
}

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
//寄存器个数
int16_t iNCoils = ( int16_t )usNCoils;
//寄存器偏移量
int16_t usBitOffset;
//检查寄存器是否在指定范围内
if( ( (int16_t)usAddress >= REG_COILS_START ) &&
( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
{
//计算寄存器偏移量
usBitOffset = ( int16_t )( usAddress - REG_COILS_START );
switch ( eMode )
{
//读操作
case MB_REG_READ:
while( iNCoils > 0 )
{
*pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );
iNCoils -= 8;
usBitOffset += 8;
}
break;
//写操作
case MB_REG_WRITE:
while( iNCoils > 0 )
{
xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,
( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),
*pucRegBuffer++ );
iNCoils -= 8;
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
//return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
//操作寄存器个数
int16_t iNDiscrete = ( int16_t )usNDiscrete;
//偏移量
uint16_t usBitOffset;
//判断寄存器时候再制定范围内
if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&
( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) )
{
//获得偏移量
usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );
while( iNDiscrete > 0 )
{
*pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,
( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );
iNDiscrete -= 8;
usBitOffset += 8;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
//return MB_ENOREG;
}

后注: 网上EncEthernet代码需要进行一些修改才能用,比如TCP端口,如果是unsigned char类型,则要改为unsigned int类型,不然端口号不能超过255,所有处理端口号的语句都要检查一遍。make_tcp_ack_with_data也要修改,因为原版里这样的
// you must have called init_len_info at some time before calling this function
// dlen is the amount of tcp data (http data) we send in this packet
// You can use this function only immediately after make_tcp_ack_from_any
// This is because this function will NOT modify the eth/ip/tcp header except for
// length and checksum
void make_tcp_ack_with_data(unsigned char *buf, unsignedint dlen)
意思是调用make_tcp_ack_with_data之前必须先给init_len_info赋值,并且调用make_tcp_ack_from_any后才能调用make_tcp_ack_with_data,要修改make_tcp_ack_with_data,使之能独立重建一个tcp包。
*这个程序在结束TCP连接的时候只用了两次挥手,而不是四次挥手,但是用起来没有问题。

    推荐阅读