自建极简Ethercat主站-底层驱动编写

1、简介

MECM(Mini Ethercat Master),名字随便起的。已经学习了一段时间的Ethercat总线了,目前的想法就是自己简单实现一个Ethercat主站,没有太多的冗余功能,暂时不考虑太多的容错机制,仅实现目前用到的FOE、COE、过程数据通信这三个功能,仅用于学习和加深理解。主站的硬件是GD32F450Z_EVAL开发板,板载的以太网芯片是DP83848VV。

2、底层编写

从根本来讲,Ethercat主站就是用来读写从站的。因此驱动的最核心功能有三个:网卡初始化、写入据到从站、读取从站数据。还有一个非必要功能,就是系统时钟,用来超时计数,也算是个简单的容错。当然,如果你不做超时也可以,选择发送和接收时一直死等也可以。

1、网卡初始化

关于GD32的以太网理解和初始化流程,请看文章Ethercat学习-GD32以太网学习 下面是部分代码,代码的初始化流程也是按照文章中的流程来的。

/* 初始化代码片段 */
int enet_system_init(void)
{
    ErrStatus reval_state = ERROR;
    /* 初始化以太网模块时钟 */
    enet_clock_init();
    /* 初始化以太网引脚 RMII接口*/
    enet_gpio_init();
    /* 初始化以太网MAC和DMA */
    reval_state = enet_mac_dma_config();
    return reval_state;
}
/* MAC 和 DMA 的初始化 */
static int enet_mac_dma_config(void)
{
    int i;
    ErrStatus reval_state = ERROR;
    /* 复位,并等待完成 */
    enet_deinit();
    reval_state = enet_software_reset();
    if(ERROR == reval_state) {
        return ERROR;
    }
    /* 以太网初始化 配置网卡自协商模式、使能接收端校验和检测功能、接收所有的广播帧*/
    reval_state = enet_init(ENET_AUTO_NEGOTIATION, ENET_AUTOCHECKSUM_DROP_FAILFRAMES, ENET_BROADCAST_FRAMES_PASS);
    if(ERROR == reval_state) {
        return ERROR;
    }
    /* 设置MAC地址 */
    enet_mac_address_set(ENET_MAC_ADDRESS0, mac);
    /* 初始化收发的描述符,常规描述符,链结构*/
    enet_descriptors_chain_init(ENET_DMA_TX);
    enet_descriptors_chain_init(ENET_DMA_RX); 
    /* 设置接收描述符 RDES1的31位为0,接收完成后会立马产生中断*/
    for(i=0; i<ENET_RXBUF_NUM; i++){ 
        enet_rx_desc_immediate_receive_complete_interrupt(&rxdesc_tab[i]);
    }
    /* 使能硬件IP包头和数据域的校验和计算和插入 */
    for(i=0; i < ENET_TXBUF_NUM; i++){
        enet_transmit_checksum_config(&txdesc_tab[i], ENET_CHECKSUM_TCPUDPICMP_FULL);
    }
//    /* 正常中断汇总使能 接收中断属于正常中断 */
//    enet_interrupt_enable(ENET_DMA_INT_NIE);
//    /* 使能接收中断 */
//    enet_interrupt_enable(ENET_DMA_INT_RIE);
    /* 使能以太网 */
    enet_enable();  
    return reval_state;
}

2、数据读写

数据的读写很简单,读数据采用的时轮询的方式,不是中断的方式。

/**
 * @******************************************************************************: 
 * @func: [enet_buf_send]
 * @description: 以太网数据发送
 * @note: 
 * @author: 
 * @param [uint8_t] *buf  待发送数据的地址
 * @param [uint32_t] length 待发送数据的长度
 * @return [*]
 * @==============================================================================: 
 */
int enet_buf_send(uint8_t *buf,uint32_t length)
{
    ErrStatus reval_state = ERROR; 
    reval_state = enet_frame_transmit(buf,length);
    return  reval_state;
}
/**
 * @******************************************************************************: 
 * @func: [enet_buf_send]
 * @description: 以太网数据接收
 * @note: 
 * @author: 
 * @param [uint8_t] *buf 接收数据的地址
 * @param [uint32_t] length 接收数据缓存的最大长度
 * @return [*]
 * @==============================================================================: 
 */
int enet_buf_recv(uint8_t *buf,uint32_t length)
{
    ErrStatus reval_state;
    if(enet_rxframe_size_get()){
        reval_state = enet_frame_receive(buf,length);
    }else{
        reval_state = 0;
    }    
    return reval_state;
}

3、系统时钟

系统时钟的思路就是用滴答定时器,每1us记一次数,这个数就时我们的系统时钟,这个数我们只用来做超时检测。

/**
 * @******************************************************************************: 
 * @func: [osal_current_time]
 * @description: 返回系统时钟计数,单位us
 * @note: 
 * @author: 
 * @return [*]
 * @==============================================================================: 
 */
uint64_t osal_current_time_us(void)
{
  return timercount;
}

3、数据打包

1、Ethercat报文简介

基本接口已经写好,接下来就是按照Ethercat的数据包对数据进行打包,然后将发送出去。

自建极简Ethercat主站-底层驱动编写_第1张图片

如图所示,一个标准的ethercat包括了6部分:

名称 说明 长度
Destination 目的MAC地址,这个可以暂时忽略,在简单的连接中,这个是真的无关紧要 6byte
Source 本机MAC地址,这个可以暂时忽略,在简单的连接中,这个是也无关紧要 6byte
EthercatType 0x88A4 2byte
EthercatHeader 有11bit的length、1bit的预留、4bit的type组成 2byte
Datagrams 数据包 46~1500byte
FCS 帧校验序列,这个不需要我们去填写,自动生成 4byte

这里我们关注两个部分EthercatHeader和Datagrams,其他的要么是固定的,要么是自动生成的,只有这两个需要我们字节计算。另外可以看到图中所示,ethercat报文最大长度写的是1522,这是因为它算上了VLAN的4byte,我们用的Ethercat总长度是1518。ethercat报文的最小长度为64,而Datagrams的最小长度就是64-6-6-2-4 = 46。如果我们的Datagrams小于44个长度,MAC子系统会自动将剩余的补齐。如下图所示,图中的pad bytes就是系统补齐的。
自建极简Ethercat主站-底层驱动编写_第2张图片

EthercatHeader中有一个length,这个长度就是Datagrams的长度,但是它不包含补齐的长度,也不包含FCS的长度,例如我们自己的数据是10个字节,然后系统又给我们填充了34个自己凑够了64字节,那么这个length的值依然是10。EthercatHeader中的type只能为0x01

2、Datagrams报文简介

Datagrams可以只包含一个Datagram,也可以是好多个Datagram拼接起来的。如下图所示:

自建极简Ethercat主站-底层驱动编写_第3张图片

在一包Datagram中又分为了三部分,DatagramHeader、data、WKC。DatagramHeader固定长度为10,说明如下:

名称 说明 长度
Cmd Ethercat命令,用来表明该数据包的作用:LWR、BRD、FPRW、APRW等 1byte
idx 索引,我们字节生成的,不会被从机修改,可以判断数据是否重复或者丢失 1byte
Address 地址。除了逻辑地址是站四个字节外,其他地址都是一个从机地址(2byte)+内存地址(2byte) 4byte
Len 数据包中的数据长度,也就是data的长度 11bit
R 预留,0 1bit
C 帧循环,由从机的处理单元自动置位,用来识别该帧是否已经循环了,例如从机下次又收到了相同的帧,但是帧循环这里已经置1了,说明该帧已经处理过一次了,由于某种原因在重复循环。 1bit
M 该数据包是否是最后一包,1表示后续还有数据包 1bit
IRQ 所有从站ethercat事件请求寄存器的OR,主机发送时写0就好了。值是在从站读取出来的。 2byte

3、数据组包

int mecm_BRD_test()
{
    int i = 0;
    mecm_fram_t fram;
    mecm_datagram_header_t dg_header;
    /* 填充Destination、Source、EthercatType */
    for(i = 0;i<MAXBUF;i++)
    {
        memcpy( fram.en_header.dst,dstMAC,6);
        memcpy( fram.en_header.src,srcMAC,6);
        fram.en_header.etype = 0x88A4;
    }
    /* 填充Ethercat header */
    fram.ec_header.clehgth = 13;
    fram.ec_header.reserve = 0;
    fram.ec_header.ctype   = 0x01;
    /* 填充数据包的datagram header */
    dg_header.cmd     = CMD_BRD;
    dg_header.idx     = 1;
    dg_header.addr.np.adp = 0x0001;
    dg_header.addr.np.ado = 0x0508;
    /* 数据长度为 1 */
    dg_header.dlength = 1;  
    dg_header.irq     = 0x0000; 
    memcpy((uint8_t *)fram.datagram,(uint8_t *)&dg_header,10);
    /* 填充数据包 data */
    fram.datagram[10] = 0x01;
    /* 填充WCK 发送时置 0*/
    uint16_t wck = 0;
    memcpy((uint8_t *)&fram.datagram[11],(uint8_t *)&wck,2);
    mecm_data_send((uint8_t *)&fram,29);
}

上电测试,开发板于电脑网口连接,用wireshark抓包,结果如下:

自建极简Ethercat主站-底层驱动编写_第4张图片

可以看到,虽然数据发出来了,但是wireshark识别的并不时Ethercat包,0x88A4 变成了0xA488。这里要把上面的0x88A4改为0xA488,主要是因为本地是小端存储,而网络字节序中在前的被认为是高字节,改完之后可以看到,ethercat包被正确识别出来了,结果如下:

自建极简Ethercat主站-底层驱动编写_第5张图片

4、其他

关于网络字节序的理解。我们用的单片机时小端存储,低字节的放在低地址处,数据在传输的时候是由低到高。因此我们的0x88A4,首先传输的时0xA4。但是到了对方网卡那里,它是按照网络字节序来识别的,而网络字节序统一是高字节在前,因此网卡在识别协议的时候先到达的0xA4被认为是高字节,所以就被识别为了0xA488。因此我们在传输前可以将网络协议部分的报文转换为网络字节序,例如MAC地址,以太网类型等报文,这样网卡在识别网络协议的时候可以识别出来。至于数据部分,我们可以不需要管,因为主机和从机都是小端存储,不会出错。当然,如果接收方是大端的话,数据部分需要转换为大端再传输,或者让接收方自己收到数据后自己转换。

你可能感兴趣的:(Ethercat,学习)