EtherCAT FoE简介
FoE(File Access over EtherCAT)可实现EtherCAT节点之间的文件传输,本文介绍FoE的基本原理,以及FoE在开源EtherCAT主站Etherlab中的实现过程。一、软件更新方式在产品开发调试过程中,我们一般使用仿真器更新程序。当产品发布后,我们通常使用串口、CAN或者WiFi等端口更新程序。如果是EtherCAT从站设备,使用FoE在bootstrap...
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
更多推荐
所有评论(0)