学习 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 USHORT   usRegInputBuf[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 UCHAR       ucRegCoilsBuf[REG_COILS_SIZE / 8 + (REG_COILS_SIZE % 8 ? 1 : 0)] = {0};
#define REG_DISCRETE_START 1  //初始地址为0会有问题
#define REG_DISCRETE_SIZE 32
static UCHAR       ucRegDiscreteBuf[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 int  info_data_len; //tcp数据长度,这个定义在EncEthernet里定义了
unsigned int info_len; //tcp数据长度
int main()
{
         unsigned int plen; //enc28j60接收数据长度
         unsigned int dat_p; //TCP数据在buf里的位置
         eMBErrorCode    eStatus; 
         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 还需要实现几个函数
 

BOOL  xMBTCPPortInit( USHORT usTCPPort )
{
    if( usTCPPort == 0 ) return FALSE;
    else return TRUE;
}

void vMBTCPPortDisable( )
{}

FreeModbusTCP 从xMBTCPPortGetRequest函数获得TCP数据,经过处理后,调用xMBTCPPortSendResponse函数,将处理结果数据作为参数传递,用户在该函数获得处理后的modbus报文,进行进一步处理。
BOOL  xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
{
    *ppucMBTCPFrame = info_addr; //TCP数据地址
    *usTCPLength = info_len; //TCP数据长度
    return TRUE;
}
BOOL xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
{

    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通用,这个是从网上抄的,感谢!
eMBErrorCode  eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    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, unsigned  int 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连接的时候只用了两次挥手,而不是四次挥手,但是用起来没有问题。

 

你可能感兴趣的:(单片机开发)