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