FoE(File Access over EtherCAT)可实现EtherCAT节点之间的文件传输,本文介绍FoE的基本原理,以及FoE在开源EtherCAT主站Etherlab中的实现过程。

一、软件更新方式

在嵌入式产品开发调试过程中,我们一般使用仿真器更新程序。当产品发布后,我们通常使用串口、CAN或者蓝牙等端口更新程序。如果是EtherCAT从站设备,使用FoE在bootstrap模式也可以实现更新程序的功能。
这里写图片描述
如上图所示,应用程序通常为.bin格式的文件,也有的使用.hex或者.s19格式的。将应用程序bin文件更新到从站的Flash,涉及到3部分:
(1) EtherCAT主站,负责读取bin文件的内容,并按FoE的格式发送到从站,如Twincat在从站的online界面使用download和upload按钮操作即可,本文使用的主站为Etherlab。
(2) 从站协议栈,包含FoE功能,负责接收和解析主站发送的FoE帧。
(3) 烧录功能,将接收到的bin文件内容烧录到Flash,这部分功能与芯片类型密切相关,不同的芯片烧录Flash的步骤,提供的API都不尽相同,通常还需要将Flash分片,分别存储bootloader代码和应用层代码。

二、FoE帧格式

FoE帧格式如下图所示:
这里写图片描述
其中OpCode的取值范围为1-6,不同的值代表不同的功能,data中数据的含义也不同。

当OpCode =1 时,表示读文件请求:
这里写图片描述
其中Password为0时表示不需要密码。

当OpCode=2时,表示写文件请求:
这里写图片描述

当OpCode=3时,表示写入数据:
这里写图片描述
其中Packet Number表示数据包计数,每发送一次数据包加1,通信双方使用该值保证数据包按顺序收发。

当OpCode=4时,表示对接收数据的应答:
这里写图片描述

当OpCode=5时,表示发生错误,包含错误代码和描述:
这里写图片描述

当OpCode=6时,表示传输百分比等内容:
这里写图片描述

三、FoE实例

作为示例,使用Ubuntu中安装的Etherlab主站,将bin文件TestFoE.bin下载到从站。
TestFoE.bin文件的部分内容如下:
这里写图片描述
将TestFoE.bin拷贝到/opt/FoE目录下,并在终端输入命令:

 #ethercat foe_write -p 0 TestFoE.bin

等待文件传输完成:
这里写图片描述
在总线上可监测到FoE相关的数据。
这里写图片描述

四、Etherlab FoE源码简析

执行FoE命令时,Etherlab的执行流程大致如下图所示:
这里写图片描述
1、读取文件内容
在终端输入foe_write命令后,首先执行的是CommandFoeWrite::execute(),主要完成:
(1)打开文件,并拷贝文件内容。
(2)调用对应从站的foe写功能。

void CommandFoeWrite::execute(const StringVector &args)
{
    ...
    file.open(args[0].c_str(), ifstream::in | ifstream::binary);
    ...
    loadFoeData(&data, file); //将文件内容拷贝到data
    ...
    try {
        m.writeFoe(&data);
    } catch (MasterDeviceException &e) {
    ...
}

其中,m.writeFoe(&data)将调用ec_ioctl_slave_foe_write(),将foe写请求挂载到从站的foe请求队列中:

static ATTRIBUTES int ec_ioctl_slave_foe_write(
        ec_master_t *master, /**< EtherCAT master. */
        void *arg /**< ioctl() argument. */
        )
{
    ...
    // schedule FoE write request.
    list_add_tail(&request.list, &slave->foe_requests);
    ...
}

在ec_fsm_slave_action_process_foe()中检测到foe队列中有写请求,则开始foe写状态机:

int ec_fsm_slave_action_process_foe(
        ec_fsm_slave_t *fsm, /**< Slave state machine. */
        ec_datagram_t *datagram /**< Datagram to use. */
        )
{
    ...
    // take the first request to be processed
    request = list_entry(slave->foe_requests.next, ec_foe_request_t, list); 
    ...
    fsm->state = ec_fsm_slave_state_foe_request;
    ec_fsm_foe_transfer(&fsm->fsm_foe, slave, request);//调用ec_fsm_foe_write_start
    ec_fsm_foe_exec(&fsm->fsm_foe, datagram);
    return 1;      
}

2、发送写请求
foe写状态机开始执行的是ec_fsm_foe_write_start(),在其中将调用ec_foe_prepare_wrq_send()发送OpCode=2的写请求:

int ec_foe_prepare_wrq_send(
        ec_fsm_foe_t *fsm, /**< Finite state machine. */
        ec_datagram_t *datagram /**< Datagram to use. */
        )
{
    ...
    current_size = fsm->tx_filename_len;

    data = ec_slave_mbox_prepare_send(fsm->slave, datagram,
            EC_MBOX_TYPE_FILEACCESS, current_size + EC_FOE_HEADER_SIZE);
    ...
    EC_WRITE_U16( data, EC_FOE_OPCODE_WRQ); // fsm write request
    EC_WRITE_U32( data + 2, fsm->tx_packet_no );

    memcpy(data + EC_FOE_HEADER_SIZE, fsm->tx_filename, current_size);
    return 0;
}

通过wireshark监控到对应的帧如下:
这里写图片描述

3、等待从站ACK

发送完写请求后,将等待从站返回的ACK,若收到ACK,则开始发送数据:

void ec_fsm_foe_state_ack_read(
        ec_fsm_foe_t *fsm, /**< FoE statemachine. */
        ec_datagram_t *datagram /**< Datagram to use. */
        )
{
     ...
    if (opCode == EC_FOE_OPCODE_ACK) {
        fsm->tx_packet_no++;
        fsm->tx_buffer_offset += fsm->tx_current_size;
        if (fsm->tx_last_packet) {       
            fsm->state = ec_fsm_foe_end;//如果最后一帧已经发送,则结束真个FoE状态机
            return;
        }

        if (ec_foe_prepare_data_send(fsm, datagram)) {
            ec_foe_set_tx_error(fsm, FOE_PROT_ERROR);
            return;
        }
        fsm->state = ec_fsm_foe_state_data_sent;
        return;
    }   
}

ACK帧:
这里写图片描述

4、发送数据
在ec_foe_prepare_data_send()发送数据:

int ec_foe_prepare_data_send(
        ec_fsm_foe_t *fsm, /**< Finite state machine. */
        ec_datagram_t *datagram /**< Datagram to use. */
        )
{
    size_t remaining_size, current_size;
    uint8_t *data;

    remaining_size = fsm->tx_buffer_size - fsm->tx_buffer_offset;
    //如果等待发送的数据长度小于一个邮箱能装载的最大数据,即本次发送能把剩下的数据一次发送完成
    //本实验从站的邮箱大小设为128字节,减去邮箱头12字节和FoE帧头12字节,即一次最多可传送
    //116个字节的数据
    if (remaining_size < fsm->slave->configured_tx_mailbox_size
            - EC_MBOX_HEADER_SIZE - EC_FOE_HEADER_SIZE) {
        current_size = remaining_size;
        fsm->tx_last_packet = 1;
    } else {
        current_size = fsm->slave->configured_tx_mailbox_size
            - EC_MBOX_HEADER_SIZE - EC_FOE_HEADER_SIZE;
    }

    data = ec_slave_mbox_prepare_send(fsm->slave,
            datagram, EC_MBOX_TYPE_FILEACCESS,
            current_size + EC_FOE_HEADER_SIZE);
    if (IS_ERR(data)) {
        return -1;
    }

    EC_WRITE_U16(data, EC_FOE_OPCODE_DATA);    // OpCode = DataBlock req.
    EC_WRITE_U32(data + 2, fsm->tx_packet_no); // PacketNo, Password

    memcpy(data + EC_FOE_HEADER_SIZE,
            fsm->tx_buffer + fsm->tx_buffer_offset, current_size);
    fsm->tx_current_size = current_size;

    return 0;
}

数据帧:
这里写图片描述
从上图可以看出,由于邮箱大小设为128字节,一次最多传送116字节。
传输的第一帧数据中的内容即是TestFoE.bin文件最开始的内容。

发送完数据后,Etherlab将等待从站返回的ACK。

5、发送完成
当tx_last_packet标志为1时,表示所有的数据发送完成,最后一帧数据:
这里写图片描述
从上图可以看出,最后一帧数据的字节数68,小于一帧数据最大装载量116。
TestFoE.bin文件总共经过了411次写数据/应答的传输过程。

五、参考资料

1.ETG100.6 V1.0.3.2

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐