EtherCAT主站的许多部分都是以有限状态机(FSM)来实现的。虽然这导致在某些方面增加了复杂性,但是也提供了许多新的可能性。
下面的短代码示例表明了如何读取所有从站状态,并且还说明了“顺序化”编程的限制。
1 ec_datagram_brd( datagram , 0x0130 , 2); // prepare datagram
2 if( ec_master_simple_io (master , datagram )) return -1;
3 slave_states = EC_READ_U8 ( datagram -> data ); // process datagram
ec_master_simple_io()函数提供了一个简单的接口,用于同步发送单个数据报并接收其结果1。 在内部,它将指定的数据报进行排队,调用ec_master_send_datagrams()函数发送一个带有队列数据报的帧,然后主动等待其接收。
这种顺序化方法非常简单,仅反映在三行代码中。 缺点是,主站在等待数据报接收的时间内被阻塞。 当只有一个实例使用主站时并没有困难,但是如果更多的实例想(同步地)使用主站,则不可避免地要考虑顺序化模型的替代方案。
如果希望多个实例同步发送和接收数据报,必须对主站访问进行顺序化。 使用现在的方法,将导致每个实例都要有一个动态等待的阶段,由于巨大的时间开销,尤其在实时环境中,这是不可接受的。
一种可能的解决方案是,顺序化地执行所有实例,以对其数据报进行排队,然后将控制权提供给下一个实例,而不是等待数据报接收。 最后,总线IO由更高等级的实例完成,这意味着所有排队的数据报都被发送和接收。 下一步是再次执行所有实例,然后处理它们接收的数据报并发出新的数据报。
使用这种方法的结果是所有实例在将控制权返回到较高等级的实例时必须保持它们的状态。 显而易见,在这种情况下应当使用有限状态机模型。 第5.1节将介绍一些用到的理论,而下面的列表显示了通过将上面的示例编写为状态机的基本方法:
1 // state 1
2 ec_datagram_brd ( datagram , 0x0130 , 2); // prepare datagram
3 ec_master_queue (master , datagram ); // queue datagram
4 next_state = state_2 ;
5 // state processing finished
在所有的实例执行它们的当前状态并且将它们的数据报进行排队之后,数据报将被发送和接收。 然后执行各自的下一状态:
1 // state 2
2 if ( datagram -> state != EC_DGRAM_STATE_RECEIVED ) {
3 next_state = state_error ;
4 return; // state processing finished
5 }
6 slave_states = EC_READ_U8 ( datagram -> data ); // process datagram
7 // state processing finished .
有关主站代码中使用的状态机编程概念的介绍,请参见第5.2节。
有限状态机[9]是一种具有输入和输出的行为模型,其中输出不仅取决于输入,还取决于输入的历史。有限状态机(或有限自动机)的数学定义是一个六元组(Σ,Γ,S,s0,δ,ω),其中
状态转移函数δ通常由状态转移表或状态转移图指定。转移表提供了状态机行为的矩阵视图(见表5.1)。矩阵的行对应于状态(S = {s0,s1,s2}),列对应于输入符号(Γ= {a,b,ε})。某一行i和列j中的内容表示在状态si中读取某个输入符号σj的情况下的下一状态(以及可能的输出)。
表5.1:典型的状态转换表
a | b | ε |
---|---|---|
s0 | s1 | s1 |
s1 | s2 | s1 |
s2 | s0 | s0 |
同一示例的状态图类似于图5.1中的状态图。 状态被表示为圆或椭圆,并且转移被绘制为它们之间的箭头。 转移箭头旁边是必须满足以允许转移的条件。 初始状态由带有指向相应状态的箭头的黑色实心圆圈标记。
图5.1:典型的状态转移图
确定性和非确定性状态机 状态机可以是确定性的,意味着对于一个状态和输入,存在一个(且仅一个)跟随状态。 在这种情况下,状态机只有一个起始状态。 非确定性状态机对于单个状态输入组合可以具有多于一个的转移。 在后一种情况下存在一组起始状态。
Moore和Mealy机器 所谓的Moore机器和Mealy机器是有区别的。 数学上讲,区别在于输出函数ω:如果它仅仅取决于当前状态(ω:S → Γ),则机器对应于“Moore 模型”。否则,如果 ω 是状态和输入字母表的函数(ω:S×Σ → Γ), 状态机对应于“Mealy模型”。Mealy机在大多数情况下是更实用的解决方案,因为它们的设计允许机器具有最少的状态数。 在实际中,经常使用两种模型的混合。
对状态机的误解 有一种称为“状态爆炸”的现象,通常被认为是反对在复杂环境中使用状态机的论断。 必须提到的是,这一点是有误导性的[10]。 状态爆炸通常是由于状态机设计不良而发生的:常见的错误是将所有输入的当前值存储在一个状态中,或者没将复杂状态机划分为更简单的子状态机。 EtherCAT主站使用几个状态机,它们分层执行,因此用作子状态机。 这些也在下面进行描述。
本节将介绍主站中用于实现状态机的技术。
状态机编程 有一些方法可以在C代码中实现状态机。 一个明显的方式是通过一个大case来实现不同的状态和行为:
1 enum { STATE_1 , STATE_2 , STATE_3 };
2 int state = STATE_1 ;
3
4 void state_machine_run (void * priv_data ) {
5 switch ( state ) {
6 case STATE_1 :
7 action_1 ();
8 state = STATE_2 ;
9 break;
10 case STATE_2 :
11 action_2 ()
12 if ( some_condition ) state = STATE_1 ;
13 else state = STATE_3 ;
14 break;
15 case STATE_3 :
16 action_3 ();
17 state = STATE_1 ;
18 break;
19 }
20 }
对于小型状态机,这是一个选择。 但缺点是,随着状态数量的增加,代码会很快变得复杂,并且每次运行都要执行对附加情况的区分。 此外,还会浪费大量的缩进。
主站中使用的方法是每个状态在其自身函数中实现,并使用函数指针存储当前的状态函数:
1 void (* state )(void *) = state1 ;
2
3 void state_machine_run (void * priv_data ) {
4 state ( priv_data );
5 }
6
7 void state1 (void * priv_data ) {
8 action_1 ();
9 state = state2 ;
10 }
11
12 void state2 (void * priv_data ) {
13 action_2 ();
14 if ( some_condition ) state = state1 ;
15 else state = state2 ;
16 }
17
18 void state3 (void * priv_data ) {
19 action_3 ();
20 state = state1 ;
21 }
在主站代码中,所有状态机3的状态指针聚集在 ec_fsm_master_t 类的单个对象中。 这是有好处的,因为始终存在每个状态机的一个实例,并且可以按需启动。
Mealy和Moore 如果仔细观察上述列表,可以看出所执行的动作(状态机的“输出”)仅取决于当前状态。 这符合5.1节中介绍的“Moore”模型。 如上所述,“Mealy”模型提供了更高的灵活性,可以在下面的列表中看到:
1 void state7 (void * priv_data ) {
2 if ( some_condition ) {
3 action_7a ();
4 state = state1 ;
5 }
6 else {
7 action_7b ();
8 state = state8 ;
9 }
10 }
(3)+(7)状态函数根据即将要完成的状态转移执行动作。
最灵活的替代方案是根据状态执行某些动作,状态之后是取决于状态转移的一些动作:
1 void state9 (void * priv_data ) {
2 action_9 ();
3 if ( some_condition ) {
4 action_9a ();
5 state = state7 ;
6 }
7 else {
8 action_9b ();
9 state = state10 ;
10 }
11 }
这个模型经常用在主站中。 它结合了两种方法各自的优点。
使用子状态机 为了避免状态过多,EtherCAT主站状态机的某些功能已经被引出到子状态机中。这有助于封装相关的工作流,并且还避免了第5.1节中描述的“状态爆炸”现象。如果主站使用一个大状态机,状态的数量将是实际数量好多倍。这将导致复杂程度增加到不可管理的级别。
执行子状态机 如果状态机开始执行子状态机,它通常保持在一个状态,直到子状态机终止。 这通常是像下面的列表中那样完成,它取自从站配置状态机器的代码:
1 void ec_fsm_slaveconf_safeop ( ec_fsm_t *fsm)
2 {
3 fsm -> change_state (fsm ); // execute state change
4 // sub state machine
5
6
if (fsm -> change_state == ec_fsm_error ) {
7 fsm -> slave_state = ec_fsm_end ;
8 return;
9 }
10
11 if (fsm -> change_state != ec_fsm_end ) return;
12
13 // continue state processing
14 ...
(3)change_state 是状态改变状态机的状态指针。 指针指向的状态函数将被执行…
(6)…直到状态机以错误状态终止…
(11)…或直到状态机终止于结束状态。直到那时,“更高”的状态机保持在当前状态,并在下一个周期中再次执行子状态机。
状态机的描述 以下部分描述了EtherCAT主站中使用的每个状态机。 状态机的文本描述包含了对相应状态转换图中转移的引用,这些转换用箭头标记,后面跟着连续状态的名称。 没有明确描述由琐碎的错误情况(即,没有来自从站的响应)引起的转移。 这些转移在图中被绘制为虚线箭头。
主站状态机在主站线程的上下文中执行。 图5.2显示了其转移图。
图5.2:主站状态机的转移图
总线监控 监视总线拓扑。 如果更改,则(重新)扫描总线。
从站配置 监视从站的应用程序层状态。 如果从站没有处于其应该处于的状态,则从站被(重新)配置。
请求处理 处理(源自应用程序或外部源的)请求。 请求是主站应该异步处理的工作,例如SII访问,SDO访问等。
从站扫描状态机(可以在图5.3中看到),表示了读取期望的从机信息的过程。
图5.3:从站扫描状态机的转移图
扫描过程包括以下步骤:
节点地址 为从站设置节点地址,以便对所有后续操作进行节点寻址。
AL状态 读取初始应用层状态。
基本信息 从较低的物理内存中读取基本信息(如支持的FMMU的数量)。
数据链接 读取有关物理端口的信息。
SII大小 确定SII内容的大小以分配SII映像存储器。
SII数据 SII内容被读入主站的映像。
PREOP 如果从站支持CoE,则使用状态改变FSM(见第5.6节)将其设置为PREOP状态,以启用邮箱通信,并通过CoE读取PDO配置。
PDO 通过CoE(如果支持)使用PDO读取FSM(参见第5.8节)来读取PDO。 如果成功,覆盖来自SII的PDO信息(如果有的话)。
从站配置状态机(可以在图5.4中看到)表示了配置从站并将其带到特定应用层状态的过程。
图5.4:从站配置状态机的转移图
INIT 状态改变FSM用于使从站进入INIT状态。
FMMU清除 为了避免从站对任何过程数据都做出反应,FMMU配置将被清除。 如果从站不支持FMMU,则跳过此状态。 如果INIT是请求的状态,则状态机完成。
邮箱同步管理器配置 如果从站支持邮箱通信,则配置邮箱同步管理器。 否则,将跳过此状态。
PREOP 状态改变FSM用于使从站进入PREOP状态。 如果这是请求的状态,则状态机完成。
SDO配置 如果有绑定从站配置(参见第3.1节),并且应用程序提供了SDO配置,则这些配置将发送到从站。
PDO配置 执行PDO配置状态机以应用所有必需的PDO配置。
PDO同步管理器配置 如果存在任何PDO同步管理器,则会对其进行配置。
FMMU配置 如果存在由应用程序提供的FMMU配置(即,如果应用程序注册了PDO条目),则应用它们。
SAFEOP 状态改变FSM用于使从站进入SAFEOP状态。 如果这是请求的状态,则状态机完成。
OP 状态改变FSM用于使从机进入OP状态。 如果这是请求的状态,则状态机完成。
状态改变状态机可以在图5.5中看到,表示了改变从站的应用层状态的过程。 它实现了[3,sec. 6.4.1]中描述的状态和转移。
图5.5:状态变化状态机的转移图
Start 通过“AL控制请求”寄存器请求新的应用层状态(见[3, sec. 5.3.1])。
检查响应 一些从站需要一些时间来响应AL状态改变命令,并且一段时间内不会响应。 对于这种情况,将再次发出命令,直到它被确认。
检查AL状态 如果AL状态改变数据报被确认,则必须读出“AL控制响应”寄存器(见[3, sec. 5.3.2]),直到从站改变AL状态。
AL状态代码 如果从站拒绝状态更改命令,可以从“AL状态更改”寄存器中的“AL状态代码”字段读取原因(参见[3, sec. 5.3.3])。
确认状态 如果状态改变不成功,主站必须通过再次写入“AL控制请求”寄存器来确认旧状态。
检查确认 发送确认命令后,必须再次读出“AL控制响应”寄存器。
“start_ack”状态是状态机中用于主站想要确认未被请求的自发AL状态改变情况下的快捷方式。
SII状态机(如图5.6所示)通过[2, sec. 6.4]中描述的从站信息接口实现读取或写入SII数据的过程。
图5.6:SII状态机的转移图
这是状态机读取部分的工作原理:
开始读取 读请求和被请求的字的地址被写入SII属性。
检查读取命令 如果SII读请求命令已被确认,则启动定时器。 发出一个数据报,读出SII属性中的状态和数据。
提取数据 如果读操作仍然忙(SII通常实现为E2PROM),则再次读取状态。 否则,数据将从数据报中复制。
写部分几乎类似:
开始写 请求写操作,目标地址和数据字被写入SII属性。
检查写命令 如果SII写请求命令已被确认,则启动定时器。 发出一个数据报,读出SII属性中写操作的状态。
等待忙时 如果写操作仍然忙(由最小等待时间和忙标志的状态确定),则状态机保持在该状态,以避免太早发出另一个写操作。
PDO状态机是一组状态机,其通过[3, sec. 5.6.7.4]中描述的“CoE通信区”读取或写入PDO分配和PDO映射。对于对象访问,使用 CANopen over EtherCAT 访问用到的原语(参见第6.2节),因此从站必须支持CoE邮箱协议。
PDO读取FSM 该状态机(图5.7)的目的是读取从站的完整PDO配置。它读取每个同步管理器的PDO分配,并使用PDO条目读取FSM(图5.8)来读取每个分配的PDO的映射。
图5.7:PDO读取状态机的转移图
基本上,它读取每个同步管理器的PDO分配SDO(0x1C1x)元素的数量以确定为该同步管理器分配的PDO的数量,然后读出SDO的子索引以获得分配的PDO的索引。当读取PDO索引时,执行PDO条目读取FSM以读取PDO的映射PDO条目。
PDO条目读取FSM 这个状态机(图5.8)读取PDO的PDO映射(PDO条目)。它通过首先读取子索引零(元素数目)以确定映射的PDO条目的数量来读取给定PDO的相应映射SDO(0x1600 - 0x17ff,或 0x1a00 - 0x1bff)。之后,读取每个子索引以获得映射的PDO条目索引,子索引和位大小。
图5.8:PDO条目读状态机的转移图
图5.9:PDO配置状态机的转移图
图5.10:PDO条目配置状态机的转移图