EtherCAT FoE簡介

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章