当加载完毕ec_master主站模块和一个网络驱动模块后(如ec_generic),整个系统就创建一个线程用于指向空闲阶段函数(./master/master.c/ec_master_idle_thread()),在该函数中有一个过程就是执行主站状态机,也就是ec_fsm_master_exec()函数,具体实现模式就与主站中的ec_fsm_master_t类型结构有关,该类型的数据如下:

struct ec_fsm_master 
{
    ec_master_t *master; /**< master the FSM runs on 状态机的主站 */
    ec_datagram_t *datagram; /**< datagram used in the state machine 状态机用到的数据 */

	/* 数据报超时重发次数,一般设置为EC_FSM_RETRIES(3次) */
    unsigned int retries; /**< retries on datagram timeout. */

	/* 主站状态机执行函数,通过指定在不同状态下该指针指向不同函数实现,从而实现不同状态下的不同执行函数:空闲阶段: 先指向ec_fsm_master_state_start()函数实现对状态机的数据帧进行初始化,标志主机进入空闲阶段;然后再指向ec_fsm_master_state_broadcast()广播函数。*/
    void (*state)(ec_fsm_master_t *); /**< master state function */
    ec_device_index_t dev_idx; /**< Current device index (for scanning etc.). */

	/* idle表示主站状态机处于空闲阶段,该量在本文件的c文件中ec_fsm_master_reset()初始化过程中初始化为0在加载完毕ec_generic网络驱动模块之后,网络设备驱动模块创建一个内核线程执行了主站的空闲阶段函数;在空闲阶段函数中调用了ec_fsm_master_state_start()函数,该函数中设置了该量为1,标志进入空闲阶段;ec_fsm_master_state_broadcast函数中,当状态机进行重新扫描的时候,会将该idle置0,对从站配置完成后又会将该位赋值为1 */
    int idle; /**< state machine is in idle phase */

	/* 当从站个数即拓扑结构被检测到发生变化的时候,会将该rescan_required量置1,在开始主站状态机开始进行就重新扫描的时候,会将该变量赋值为当前的jiffies */
	unsigned long scan_jiffies; /**< beginning of slave scanning */

	/* 用于存储上一周期的网络设备连接状态,从而可以和本周期网络连接状态进行组合判断网络连接的断开时刻和接上的哪一刻 */
    uint8_t link_state[EC_MAX_NUM_DEVICES]; /**< Last link state for every device. */

	/* 每个网络设备接收到从站响应个数,该数值在空闲阶段是通过发送EC_DATAGRAM_BRD子报文,然后接收到的子报文获取其WKC得到 */
    unsigned int slaves_responding[EC_MAX_NUM_DEVICES]; /**< Number of responding slaves for every device. */

	/* 当从站个数即拓扑结构被检测到发生变化的时候,会将该rescan_required量置1,在开始主站状态机开始进行就重新扫描的时候,会将该位置于0 */
	unsigned int rescan_required; /**< A bus rescan is required. */

	/* 每个设备响应从站的AL状态(AL状态寄存器) */
    ec_slave_state_t slave_states[EC_MAX_NUM_DEVICES]; /**< AL states of responding slaves for every device. */
	
    ec_slave_t *slave; /**< current slave */
    ec_sii_write_request_t *sii_request; /**< SII write request */
    off_t sii_index; /**< index to SII write request data */
    ec_sdo_request_t *sdo_request; /**< SDO request to process. */

	/* 各个不同的状态机,每个状态机都有对应的不同的源码文件,如fsm_coe对应源码实现为/master/fsm_coe.h和/master/fsm_coe.c文件中 */
    ec_fsm_coe_t fsm_coe; /**< CoE state machine */
    ec_fsm_soe_t fsm_soe; /**< SoE state machine */
    ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */
    ec_fsm_change_t fsm_change; /**< State change state machine */
    ec_fsm_slave_config_t fsm_slave_config; /**< slave state machine */

	/* 从站扫描状态机,主站状态机中的子状态机 */
    ec_fsm_slave_scan_t fsm_slave_scan; /**< slave state machine */
    ec_fsm_sii_t fsm_sii; /**< SII state machine */
};

查看完毕该结构体可以看到主站状态机中还存在一些子状态机,如fsm_coe,fsm_soe,fsm_pdo,fsm_change,fsm_slave_config,基本的模式就是主站状态机在需要执行子状态机的时候,会一直等待子状态机完成相应工作,然后再执行之后的程序转入下一状态。

那么继续看主站状态机,主站状态机的状态函数会在初始化的时候初始化到./master/fsm_master.c/ec_fsm_master_state_start()函数,该函数中会对状态机子报文进行填充,进行本周期的子报文数据发送,在此使用了BRD类型子报文(关于BRD子报文的作用可以查看这部分内容)进行数据发送,通过BRD类型子报文获取从站个数,然后进入下一个状态函数,也就是./master/fsm_master.c/ec_fsm_master_state_broadcast()中进行解析。由于使用的是BRD,对应的返回的子报文的WKC(WKC计算公式参考)也就是主站连接的从站的个数,这样通过判断与之前存储的从站个数时候一致就可以判断主站连接的从站有无变化,如果判断发生变化,那么就需要进行主站对从站的重新扫描过程。

1.首先调用ec_master_clear_slaves()函数清除主站连接的从站链表,而后以刚扫描完毕得到的从站个数进行从站空间申请,并将主站的从站链表指向该位置。之后做的工作就是为新申请的从站链表空间进行内容初始化,以及对从站寄存器的一些初始化。

2.从站的配置地址存储在0x0010~0x0011,先进行对从站配置地址的清除(就是直接使用BWR报文直接写入0x0000到该寄存器);之后的配置地址写入在之后的从站扫描子状态机中进行。然后就是写入0x0900寄存器,写入该寄存器会锁存写入时候的本地时间,该时间用来使用在DC过程。

3.执行从站扫描状态机,该状态机是主站状态机中的子状态机,主站会在ec_fsm_master_state_scan_slave()函数中等待该子状态机执行完毕,至于执行是否完毕的标准就是子状态机有无正常结束或者异常结束(通过两个无内容的状态函数实现)。

4.从站扫描子状态机:该状态机的类为./master/fsm_slave_scan.h/ec_fsm_slave_scan,具体内容如下:

struct ec_fsm_slave_scan
{
    ec_slave_t *slave; /**< Slave the FSM runs on. */

    ec_datagram_t *datagram; /**< Datagram used in the state machine. */
    ec_fsm_slave_config_t *fsm_slave_config; /**< Slave configuration state machine to use. */
    ec_fsm_pdo_t *fsm_pdo; /**< PDO configuration state machine to use. */
    unsigned int retries; /**< Retries on datagram timeout. */

    void (*state)(ec_fsm_slave_scan_t *); /**< State function. */

    uint16_t sii_offset; /**< SII offset in words. */
    ec_fsm_sii_t fsm_sii; /**< SII state machine. */
};

执行到该子状态机首先就是对从站的配置地址(0x0010~0x0011)的设置,由于最开始是从站变化导致的主站重新扫描,因此从站的配置地址是需要重新设置的,该设置地址是使用EC_DATAGRAM_APWR子报文进行设置的,自增式物理写入的方式,每个从站都会参与转发他,且每个从站对该子报文处理完毕后都会在APD位置+1,当那个从站检测到APD为0的时候,就会正式处理该子报文。因此该子报文类型适合于从站配置地址无效的情况下进行。

5.写入从站的配置地址后就可以使用配置寻址方式进行指定从站的寻址了。也就是EC_DATAGRAM_FPRD类型子报文。首先获取以下当前的从站状态(0x0130~0x0131)检测从站有无已知异常。如果没有就可以继续之后的获取从站基础信息。

6.ec_fsm_slave_scan_state_base()函数用于获取从站基础信息,主要包括FMMU个数、sync个数,FMMU是否支持位操作,从在哪是否支持DC,从站DC时间范围等信息。如果从站支持DC系统,那么就先进行DC的一些设置(关于EtherCAT的DC系统了解的还不是太透彻,之后正确了解后单独写一章)。

7.获取DL状态:DL也就Data link,表示数据链路状态。他在0x0110~0x0111寄存器中存放。分别针对从站4个端口有无连接,有无回路闭合和信号是否有检测到进行信息获取。

8.获取SII信息,SII信息存放在EEPROM中,且第一个SII信息存放在EEPROM的0x40位置,然后利用SII信息获取子状态机对SII信息进行获取(关于SII信息获取子状态机需要单独列一章),大体来说就是根据SII每个项的存储格式可以获取到最终存储SII信息占据的空间大小,而后根据该空间大小将所有的SII信息获取复制到内存中,之后再进行SII信息的提取解析(./master/fsm_slave_scan.c/ec_fsm_slave_scan_state_sii_data())。

9.提取完毕SII信息后,如果支持从站COE协议,那么就继续执行./master/fsm_slave_scan.c/ec_fsm_slave_scan_enter_preop()状态函数,该状态函数中启用了从站配置子状态机,该状态机器会进行从站状态转换过程中一系列配置设置以及设置从站状态向目标设置状态变更(关于从站配置子状态机需要单独列一章)。

10.大体上一个从站的由init状态转变向preop状态的流程如上,之后就是依次将主站连接的每个从站都执行一次上述流程。之后不断做从站的重新扫描。