欢迎来到老马的领地~ 这是“压风式散热底座”发明者的个人网站:) 本人QQ:80524554,用户群1:562279766
SOEM - Simple Open EtherCAT Master Library,一个开源的EtherCAT主站库.
以前只是使用基于EtherCAT的硬件来干活,这次也因为干活的因素,需要自己写主站,就买了个开发板,结果里面例程中是移植了它.
摸了一段时间了,大概性地总结一下这个库与从站的通讯流程吧,也许不对,但能工作,毕竟我这是初学
初始化什么的就不说了,基本固定的代码,例程都是能跑通的.
重点是[如何配置从站,并成功通讯上,]我查到的资料说是要根据从站的设备描述文件,一个XML来定,这个没错,但这个XML怎么看,怎么用,找到的资料挺乱,所以自己总结并记录下.
这里第一个关键点就是PO2SOconfig回调,这个回调中主站会向从站发送配置项,以决定:
可以注意到这里的input/output都是以[主站]的角度来说的.
当这个回调完成后,就来到了第二个关键点:input/output结构.
这个结构,需要与上面的回调中写入的描述内容一致.
搞定这两点后,就是第三个关键点了:
至此,已经能与从站通讯上了.
在PO2SOconfig回调里,还一个关键点,是第四个关键点,那就是[ec_SDOwrite()写入什么数据,向哪里写入.]
我这里给出一个自己总结出来的内容,不一定对,但在我手里的两个驱动器上是可以正常工作的.
这两个从站,一是步科的FD144S,伺服;二是东莞爱唯的EEDO-06-80,开环步进驱动器.
从两者的XML文件中来看,RxPdo都是0x1600索引下,TxPdo都是在0x1A00下.别的驱动器未知,我这里就这两个
至于这两者分配到哪,就要看SM2与SM3了,两个驱动器也一样,都是0x1C12与0x1C13:
因此,ec_SDOwrite要先在0x1C12下写入RxPdo数量,我这里只有1组,就是0x1600,于是写[1].
然后写[0x1600].再在0x1600下面写我需要输出给从站的PDO对象,我这里只用了[控制字ControlWord-0x6040]与[目标位置TargetPosition-0x607A].
TxPdo组也是一样,1组,不过PDO对象多了几个:
状态字 StatusWord-0x6041
操作模式 ModesofOperation-0x6061
实际位置 PositionActualValue-0x6064
错误码 LastErrorCode-0x603F
所以最终的结构如下图:
代码如下:
要添加更多的PDO对象,只需要修改map_1600[]与map_1a00[]数组就好.
而对应上面代码中两个数组内定义好的RxPDO与TxPDO对象组的结构如下:
再定义两个上面结构的指针变量:
这样就完成了从站的配置,并且也能与从站通讯了.
所以实际的通讯流程就是:
向outputs_EEDO->xxxxxx写入数据
从inputs_EEDO->xxxxxx读取数据
上面所有的铺垫,都是为了这两位师傅能正常运货
再结合驱动器的说明书,里面有讲都支持什么运动模式,如何进入这些模式,要设置些啥值(还是利用ec_SDOwrite()),基本就行了.
记录一下,免得以后自己忘了.
PS:
还是要吐槽一下.
这个项目本来控制系统是用一个支持脉冲电机的PLC完成的,是做好了的,直接就可以工作的.
项目中使用了一个伺服电机来开关舱门,并在开关过程中控制扭矩,以防夹人,此电机仅有这作用.
本来我寻思着,加个扭矩传感器,做成恒扭矩闭环控制就好,这多简单是吧?但是----------------
合作伙伴的项目负责人[听供应商说脉冲控制的电机已经过时了,都不怎么生产了,价格与总线电机差不多,不如用总线电机],于是就想当然地在这[只有一个电机的项目中使用了一台EtherCAT总线电机].
最终导致我这边必须把整套控制方案重做,要么换成支持EtherCAT的PLC,要么换成对应的板子.
哼哼,我也是有私心的,既然你们要听供应商的来折腾我,还不听劝(我两个月前就劝过了,[不听不听我不听,供应商说总线电机好]),那我就报了一个比较长的周期,然后选了自己开发EtherCAT主站的方案,让公司买了开发板,我自己也顺便学习学习不是?
SO,以上.
以前只是使用基于EtherCAT的硬件来干活,这次也因为干活的因素,需要自己写主站,就买了个开发板,结果里面例程中是移植了它.
摸了一段时间了,大概性地总结一下这个库与从站的通讯流程吧,也许不对,但能工作,毕竟我这是初学

初始化什么的就不说了,基本固定的代码,例程都是能跑通的.
重点是[如何配置从站,并成功通讯上,]我查到的资料说是要根据从站的设备描述文件,一个XML来定,这个没错,但这个XML怎么看,怎么用,找到的资料挺乱,所以自己总结并记录下.
这里第一个关键点就是PO2SOconfig回调,这个回调中主站会向从站发送配置项,以决定:
从站[将要收到什么样的数据 - output结构], 这是主站output给从站的数据块.
从站[将会发出什么样的数据 - input结构],这是主站在从站那里input回来的数据块.
从站[将会发出什么样的数据 - input结构],这是主站在从站那里input回来的数据块.
可以注意到这里的input/output都是以[主站]的角度来说的.
PO2SOconfig回调中,调用ec_SDOwrite()来把对应的数据块描述写到从站,从站就知道[主站要什么],以及[给主站回复什么].
当这个回调完成后,就来到了第二个关键点:input/output结构.
这个结构,需要与上面的回调中写入的描述内容一致.
搞定这两点后,就是第三个关键点了:
定期调用ec_send_processdata()与ec_receive_processdata().
至此,已经能与从站通讯上了.
在PO2SOconfig回调里,还一个关键点,是第四个关键点,那就是[ec_SDOwrite()写入什么数据,向哪里写入.]
我这里给出一个自己总结出来的内容,不一定对,但在我手里的两个驱动器上是可以正常工作的.
这两个从站,一是步科的FD144S,伺服;二是东莞爱唯的EEDO-06-80,开环步进驱动器.
从两者的XML文件中来看,RxPdo都是0x1600索引下,TxPdo都是在0x1A00下.别的驱动器未知,我这里就这两个


至于这两者分配到哪,就要看SM2与SM3了,两个驱动器也一样,都是0x1C12与0x1C13:
因此,ec_SDOwrite要先在0x1C12下写入RxPdo数量,我这里只有1组,就是0x1600,于是写[1].
然后写[0x1600].再在0x1600下面写我需要输出给从站的PDO对象,我这里只用了[控制字ControlWord-0x6040]与[目标位置TargetPosition-0x607A].
TxPdo组也是一样,1组,不过PDO对象多了几个:
状态字 StatusWord-0x6041
操作模式 ModesofOperation-0x6061
实际位置 PositionActualValue-0x6064
错误码 LastErrorCode-0x603F
所以最终的结构如下图:
代码如下:
/// @brief EEDO-06-80, Man: 0000004b ID: 00000641 Rev: 00000001
/// @param slave
/// @return
int PO2SOconfig_EEDO_06_80(uint16 slave)
{
int retval, iRet, i;
uint8 u8val;
uint16 u16val, _index;
uint32 u32val;
// 0x1c12 = SM2, 0x1600 = RxPDO, 从站的接收内容配置
uint16 map_1c12[2] = {0x0001, 0x1600};
// RxPDO组下面的PDO对象
uint32 map_1600[3] = {0x0002, //数量与下面的PDO对象数量相等
0x60400010, // Controlword //0x6040 = 索引, 00 = 子索引, 10 = 16BIT
0x607a0020, // Target position
};
// 0x1c13 = SM3, 0x1A00 = TxPDO, 从站的发送内容配置
uint16 map_1c13[2] = {0x0001, 0x1a00};
// TxPDO组下面的PDO对象
uint32 map_1a00[5] = {0x0004, //数量与下面的PDO对象数量相等
0x60410010, // Statusword
0x60610008, // Modes of operation display
0x60640020, // Position actual value
0x603f0010, // Error Code
};
retval = 0;
iRet = 0;
// *********************************** 0x1c12 = RxPDO分配
u8val = 0;
retval += ec_SDOwrite(slave, 0x1c12, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u16val = 0x1600;
retval += ec_SDOwrite(slave, 0x1c12, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTRXM);
u8val = 1;
retval += ec_SDOwrite(slave, 0x1c12, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u8val = 0;
_index = 0x1600;
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
for (i = 1; i <= map_1600[0]; i++)
{
u32val = map_1600[i];
u8val = i;
iRet = ec_SDOwrite(slave, _index, i, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTRXM);
retval += iRet;
printf(" >ec_SDOwrite(0x%X) = %d.\r\n", u32val, iRet);
}
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
// *********************************** 0x1c12 = RxPDO分配 End
// *********************************** 0x1c13 = TxPDO分配
u8val = 0;
retval += ec_SDOwrite(slave, 0x1c13, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u16val = 0x1a00;
retval += ec_SDOwrite(slave, 0x1c13, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTRXM);
u8val = 1;
retval += ec_SDOwrite(slave, 0x1c13, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u8val = 0;
_index = 0x1A00;
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
for (i = 1; i <= map_1a00[0]; i++)
{
u32val = map_1a00[i];
u8val = i;
iRet = ec_SDOwrite(slave, _index, i, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTRXM);
retval += iRet;
printf(" >ec_SDOwrite(0x%X) = %d.\r\n", u32val, iRet);
}
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
// *********************************** 0x1c13 = TxPDO分配 End
// 以下是对驱动器其它参数的初始化
// 每转脉冲数
u32val = 6400;
retval += ec_SDOwrite(slave, 0x6092, 0x01, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// 梯形速度
u32val = 500;
retval += ec_SDOwrite(slave, 0x6081, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// Accel
u32val = 100;
retval += ec_SDOwrite(slave, 0x6083, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// Decel
u32val = 100;
retval += ec_SDOwrite(slave, 0x6084, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
while(EcatError) printf("%s", ec_elist2string());
printf("EEDO-06-80 slave %d set, retval = %d\n", slave, retval);
return 1;
}
/// @param slave
/// @return
int PO2SOconfig_EEDO_06_80(uint16 slave)
{
int retval, iRet, i;
uint8 u8val;
uint16 u16val, _index;
uint32 u32val;
// 0x1c12 = SM2, 0x1600 = RxPDO, 从站的接收内容配置
uint16 map_1c12[2] = {0x0001, 0x1600};
// RxPDO组下面的PDO对象
uint32 map_1600[3] = {0x0002, //数量与下面的PDO对象数量相等
0x60400010, // Controlword //0x6040 = 索引, 00 = 子索引, 10 = 16BIT
0x607a0020, // Target position
};
// 0x1c13 = SM3, 0x1A00 = TxPDO, 从站的发送内容配置
uint16 map_1c13[2] = {0x0001, 0x1a00};
// TxPDO组下面的PDO对象
uint32 map_1a00[5] = {0x0004, //数量与下面的PDO对象数量相等
0x60410010, // Statusword
0x60610008, // Modes of operation display
0x60640020, // Position actual value
0x603f0010, // Error Code
};
retval = 0;
iRet = 0;
// *********************************** 0x1c12 = RxPDO分配
u8val = 0;
retval += ec_SDOwrite(slave, 0x1c12, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u16val = 0x1600;
retval += ec_SDOwrite(slave, 0x1c12, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTRXM);
u8val = 1;
retval += ec_SDOwrite(slave, 0x1c12, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u8val = 0;
_index = 0x1600;
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
for (i = 1; i <= map_1600[0]; i++)
{
u32val = map_1600[i];
u8val = i;
iRet = ec_SDOwrite(slave, _index, i, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTRXM);
retval += iRet;
printf(" >ec_SDOwrite(0x%X) = %d.\r\n", u32val, iRet);
}
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
// *********************************** 0x1c12 = RxPDO分配 End
// *********************************** 0x1c13 = TxPDO分配
u8val = 0;
retval += ec_SDOwrite(slave, 0x1c13, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u16val = 0x1a00;
retval += ec_SDOwrite(slave, 0x1c13, 0x01, FALSE, sizeof(u16val), &u16val, EC_TIMEOUTRXM);
u8val = 1;
retval += ec_SDOwrite(slave, 0x1c13, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
u8val = 0;
_index = 0x1A00;
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
for (i = 1; i <= map_1a00[0]; i++)
{
u32val = map_1a00[i];
u8val = i;
iRet = ec_SDOwrite(slave, _index, i, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTRXM);
retval += iRet;
printf(" >ec_SDOwrite(0x%X) = %d.\r\n", u32val, iRet);
}
retval += ec_SDOwrite(slave, _index, 0x00, FALSE, sizeof(u8val), &u8val, EC_TIMEOUTRXM);
// *********************************** 0x1c13 = TxPDO分配 End
// 以下是对驱动器其它参数的初始化
// 每转脉冲数
u32val = 6400;
retval += ec_SDOwrite(slave, 0x6092, 0x01, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// 梯形速度
u32val = 500;
retval += ec_SDOwrite(slave, 0x6081, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// Accel
u32val = 100;
retval += ec_SDOwrite(slave, 0x6083, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
// Decel
u32val = 100;
retval += ec_SDOwrite(slave, 0x6084, 0x00, FALSE, sizeof(u32val), &u32val, EC_TIMEOUTSAFE);
while(EcatError) printf("%s", ec_elist2string());
printf("EEDO-06-80 slave %d set, retval = %d\n", slave, retval);
return 1;
}
要添加更多的PDO对象,只需要修改map_1600[]与map_1a00[]数组就好.
而对应上面代码中两个数组内定义好的RxPDO与TxPDO对象组的结构如下:
// ************************************************* EEDO-06-80
// 0x60400010, // Controlword
// 0x607a0020, // Target position
typedef struct
{
uint16 ControlWord; //0x6040
int32 TargetPos; //0x607A
} __attribute__((packed)) PDO_Output_EEDO;
// 0x60410010, // Statusword
// 0x60610008, // Modes of operation display
// 0x60640020, // Position actual value
// 0x603f0010, // Error Code
typedef struct
{
uint16 StatusWord;
uint8 CurrentMode;
int32 CurrentPos;
uint16 ErrorCode;
} __attribute__((packed)) PDO_Input_EEDO;
// ************************************************* EEDO-06-80 End
// 0x60400010, // Controlword
// 0x607a0020, // Target position
typedef struct
{
uint16 ControlWord; //0x6040
int32 TargetPos; //0x607A
} __attribute__((packed)) PDO_Output_EEDO;
// 0x60410010, // Statusword
// 0x60610008, // Modes of operation display
// 0x60640020, // Position actual value
// 0x603f0010, // Error Code
typedef struct
{
uint16 StatusWord;
uint8 CurrentMode;
int32 CurrentPos;
uint16 ErrorCode;
} __attribute__((packed)) PDO_Input_EEDO;
// ************************************************* EEDO-06-80 End
再定义两个上面结构的指针变量:
PDO_Output_EEDO *outputs_EEDO;
PDO_Input_EEDO *inputs_EEDO;
PDO_Input_EEDO *inputs_EEDO;
这样就完成了从站的配置,并且也能与从站通讯了.
所以实际的通讯流程就是:
向outputs_EEDO->xxxxxx写入数据
从inputs_EEDO->xxxxxx读取数据
上面所有的铺垫,都是为了这两位师傅能正常运货

再结合驱动器的说明书,里面有讲都支持什么运动模式,如何进入这些模式,要设置些啥值(还是利用ec_SDOwrite()),基本就行了.
记录一下,免得以后自己忘了.
PS:
还是要吐槽一下.
这个项目本来控制系统是用一个支持脉冲电机的PLC完成的,是做好了的,直接就可以工作的.
项目中使用了一个伺服电机来开关舱门,并在开关过程中控制扭矩,以防夹人,此电机仅有这作用.
本来我寻思着,加个扭矩传感器,做成恒扭矩闭环控制就好,这多简单是吧?但是----------------
合作伙伴的项目负责人[听供应商说脉冲控制的电机已经过时了,都不怎么生产了,价格与总线电机差不多,不如用总线电机],于是就想当然地在这[只有一个电机的项目中使用了一台EtherCAT总线电机].
最终导致我这边必须把整套控制方案重做,要么换成支持EtherCAT的PLC,要么换成对应的板子.
哼哼,我也是有私心的,既然你们要听供应商的来折腾我,还不听劝(我两个月前就劝过了,[不听不听我不听,供应商说总线电机好]),那我就报了一个比较长的周期,然后选了自己开发EtherCAT主站的方案,让公司买了开发板,我自己也顺便学习学习不是?


SO,以上.
添加评论
GB2312 https://www.m5home.com/blog/trackback.php?id=150&encode=gb2312
UTF-8 https://www.m5home.com/blog/trackback.php?id=150&encode=utf-8