分析Windows版本的Demo,以便之后移植到STM32F746上。
1. slaveinfo.exe入口函数为main,需要一个以太网PHY的网卡名,网卡名在Windows下是\Device\NPF_{XXXXXXX...XXXXXXXXX},在STM32F746下如何表示?
2. 首先需要调用ec_init,初始化网卡,绑定socket
/* initialise SOEM, bind socket to ifname */
if (ec_init(ifname))
{
printf("ec_init on %s succeeded.\n",ifname);
ec_init位于ethercatmain.c中,ec_init里边是ecx_init,再往里边是ecx_setupnic,exc_setupnic位于nicdrv.c中,这个nicdrv.c相当于Windows下的网卡驱动。值得注意的是ecx_contextt结构体,这个结构体定义在ethercatmain.h中
/** Context structure , referenced by all ecx functions*/
typedef struct ecx_context ecx_contextt;
struct ecx_context
{
/** port reference, may include red_port */
ecx_portt *port;
/** slavelist reference */
ec_slavet *slavelist;
/** number of slaves found in configuration */
int *slavecount;
/** maximum number of slaves allowed in slavelist */
int maxslave;
/** grouplist reference */
ec_groupt *grouplist;
/** maximum number of groups allowed in grouplist */
int maxgroup;
/** internal, reference to eeprom cache buffer */
uint8 *esibuf;
/** internal, reference to eeprom cache map */
uint32 *esimap;
/** internal, current slave for eeprom cache */
uint16 esislave;
/** internal, reference to error list */
ec_eringt *elist;
/** internal, reference to processdata stack buffer info */
ec_idxstackT *idxstack;
/** reference to ecaterror state */
boolean *ecaterror;
/** internal, position of DC datagram in process data packet */
uint16 DCtO;
/** internal, length of DC datagram */
uint16 DCl;
/** reference to last DC time from slaves */
int64 *DCtime;
/** internal, SM buffer */
ec_SMcommtypet *SMcommtype;
/** internal, PDO assign list */
ec_PDOassignt *PDOassign;
/** internal, PDO description list */
ec_PDOdesct *PDOdesc;
/** internal, SM list from eeprom */
ec_eepromSMt *eepSM;
/** internal, FMMU list from eeprom */
ec_eepromFMMUt *eepFMMU;
/** registered FoE hook */
int (*FOEhook)(uint16 slave, int packetnumber, int datasize);
/** registered EoE hook */
int (*EOEhook)(ecx_contextt * context, uint16 slave, void * eoembx);
};
在exc_setupnic中,入参是ecx_context.port,网卡名,false(单网卡模式)。该函数作用是绑定socket
/** Basic setup to connect NIC to socket.
* @param[in] port = port context struct
* @param[in] ifname = Name of NIC device, f.e. "eth0"
* @param[in] secondary = if >0 then use secondary stack instead of primary
* @return >0 if succeeded
*/
int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary)
由于secondary是false(单网卡模式),因此直接进入else
else
{
InitializeCriticalSection(&(port->getindex_mutex));
InitializeCriticalSection(&(port->tx_mutex));
InitializeCriticalSection(&(port->rx_mutex));
port->sockhandle = NULL;
port->lastidx = 0;
port->redstate = ECT_RED_NONE;
port->stack.sock = &(port->sockhandle);
port->stack.txbuf = &(port->txbuf);
port->stack.txbuflength = &(port->txbuflength);
port->stack.tempbuf = &(port->tempinbuf);
port->stack.rxbuf = &(port->rxbuf);
port->stack.rxbufstat = &(port->rxbufstat);
port->stack.rxsa = &(port->rxsa);
ecx_clear_rxbufstat(&(port->rxbufstat[0]));
psock = &(port->sockhandle);
}
InitializeCriticalSection作用是初始化一个临界资源对象。详情见百度。“临界区”CCriticalSection是临界资源对象指针,该函数无返回值。单进程的各个线程可以使用临界资源对象来解决同步互斥问题,该对象不能保证哪个线程能够获得到临界资源对象,该系统能公平的对待每一个线程。
之后就是配置port
再之后就是用Winpcap,Winpcap与socket区别见 https://blog.csdn.net/code_while/article/details/80234336
我们使用Winpcap发送RAW数据包 https://blog.csdn.net/ykiwmy/article/details/86560781
上图是来自Winpcap官方文档的一个架构图,Winpcap最底层是一个NPF(Netgroup Packet Filter,网络组包过滤)驱动,这是一个NDIS中间件驱动,所有经过网卡的数据包都会“途经”该驱动,在收到路过的数据包时,NPF可以选择仅仅统计一下包的情况或写入文件(做流量监控),或者写到一个环形缓冲区中,然后用户态程序调用wpcap.dll中一些函数,使用WinAPI和驱动程序交互,获取驱动缓冲区中的数据,则达到了监控底层数据包的目的。至于发送数据包,用户态程序调用Winpcap的SDK函数,这些函数调用dll中的方法,dll再用API和NPF通信,NPF是很最低层的驱动了,但它并不负责直接收发数据,NPF再调用更底层的网卡驱动实现数据包的发送。
pcap_open见 https://blog.csdn.net/swartz_lubel/article/details/75670474
pcap_open主要目的是使用winpcap打开网卡接口\Device\NPF_{XXXXXXX...XXXX}
然而这个winpcap在STM32F746上并没有,是否应该参考linux下的demo?
继续看ec_setupheader,作用是组以太网帧,采用广播模式发送,
/** Fill buffer with ethernet header structure.
* Destination MAC is always broadcast.
* Ethertype is always ETH_P_ECAT.
* @param[out] p = buffer
*/
void ec_setupheader(void *p)
{
ec_etherheadert *bp;
bp = p;
bp->da0 = htons(0xffff);
bp->da1 = htons(0xffff);
bp->da2 = htons(0xffff);
bp->sa0 = htons(priMAC[0]);
bp->sa1 = htons(priMAC[1]);
bp->sa2 = htons(priMAC[2]);
bp->etype = htons(ETH_P_ECAT);
}
至此,ec_init结束,作用是使用pcap_open打开网卡接口,并组以太网帧头(类型是ETH_P_ETH)。ec_init成功会返回1。
3. ec_config
int ec_config ( uint8 usetable,
void * pIOmap
)
Enumerate / map and init all slaves.
Parameters:
[in] usetable = TRUE when using configtable to init slaves, FALSE otherwise
[out] pIOmap = pointer to IOmap
Returns:
Workcounter of slave discover datagram = number of slaves found
返回值是number of slaves found,之后ec_configdc配置分布式时钟
之后就是不断地打印出ec_slave结构体数组里的slave信息。