由于经常在论坛混,近几天个人的收件箱收到了多条官方的邀请,本着却之不恭的初心,抽时间弄了一下,仓促之间做出的东西就比较潦草了。
至于其它硬件选型,I/O表,电气图纸,为节省篇幅,在此就省略不搞了,有关注需求的朋友可以参照其它朋友的帖子,大同小异,区别就是8工位抢答按钮的I区分配最好分配为一个完整连续的独立字节,如IB0此类操作,如此则可以方便仲裁检测,否则的话,还要使用中间层字节再做额外的位映射工作。
这里先说使用常规扫描编程实现抢答仲裁FB_ArbiterNew的方法,先上效果图,







从上面的几张图可以看出,当被检测的8位字节被赋予同一值时,图中为16#34,即2#0011 0100,也就是第2,4,5索引位同时出现上升沿变化时,连续三次检测,每次仲裁得到的首个上升沿工位ID都不一样,这也在一定程度上实现了“公平”的要求,没有出现连续三次都是同一工位被检测到首次触发上升沿的独占情况。
审题得知,如何实现最大程度上的公平,是本题的重心,仲裁FB的代码如下:
FUNCTION_BLOCK "FB_DetectFirstTrigger"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
Reset : Bool;
ArbiterType : Bool;//仲裁类型
Interlock : Byte;//互锁
LampsNoTrue : Byte;//对应状态指示灯无激活
DetectedByte : Byte; // 当前待检测的HMI按钮字节值
CfgByte : Byte; // 使能位掩码(1表示该位参与检测)
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
FirstTriggerID : USInt; // 检测到的首个上升沿位索引(0~7),若无上升沿则返回255
LastFirstTriggerID : USInt; //上一次检测到的首个上升沿位索引
END_VAR
VAR
LastByte : Byte; // 上一周期的字节值,用于上升沿检测
NextStart : USInt := 0; // 下一次扫描的起始位索引(0~7),实现循环优先级
LastCfg : Byte := 0; // 上一次的使能掩码,用于检测配置变化并复位起始位
TempRisingRaw : Byte; // 原始上升沿掩码(所有由0变1的位)
TempRising : Byte; // 经过使能掩码过滤后的有效上升沿掩码
TempLoopIndex : USInt; // 循环扫描时的索引变量
TempFound : Bool; // 是否找到首个上升沿的标志
TempNextIndex : USInt; // 寻找下一次起始位时的循环索引
TempNextFound : Bool; // 是否找到下一个使能位的标志
END_VAR
VAR_TEMP
END_VAR
VAR CONSTANT
END_VAR
BEGIN
// FUNCTION_BLOCK FB_FirstRisingEdgeMasked
// TITLE = '公平检测字节中使能位的首个上升沿(支持配置变化复位)'
// VERSION : '1.0'
// AUTHOR : '...'
IF #Reset THEN
#LastFirstTriggerID := 0;
END_IF;
IF #ArbiterType AND #Interlock = 0 AND #LampsNoTrue = 0 THEN //满足仲裁条件,3个允许灯全亮
// 检测配置是否变化,若变化则重置起始位为0,以保证新配置下的公平起始点
IF #CfgByte <> #LastCfg THEN
#NextStart := 0;
#LastCfg := #CfgByte;
END_IF;
// 1. 计算原始上升沿(当前为1且上一次为0的位)
#TempRisingRaw := #DetectedByte AND (NOT #LastByte);
#LastByte := #DetectedByte; // 更新历史值
// 2. 仅保留使能位
#TempRising := #TempRisingRaw AND #CfgByte;
// 3. 若无上升沿,直接返回255
IF #TempRising = 0 THEN
#FirstTriggerID := 255;
// NextStart 保持不变
RETURN;
END_IF;
// 4. 从 NextStart 开始循环扫描,找到第一个上升沿的使能位
#TempFound := FALSE;
// 先扫描高位部分(NextStart 到 7)
FOR #TempLoopIndex := #NextStart TO 7 DO
IF (#TempRising AND SHL(IN := BYTE#16#01, N := #TempLoopIndex)) <> 0 THEN
#LastFirstTriggerID := #FirstTriggerID := #TempLoopIndex + 1;
#TempFound := TRUE;
EXIT;
END_IF;
END_FOR;
// 若未找到,再扫描低位部分(0 到 NextStart-1)
IF NOT #TempFound THEN
FOR #TempLoopIndex := 0 TO #NextStart - 1 DO
IF (#TempRising AND SHL(IN := BYTE#16#01, N := #TempLoopIndex)) <> 0 THEN
#LastFirstTriggerID := #FirstTriggerID := #TempLoopIndex + 1;
#TempFound := TRUE;
EXIT;
END_IF;
END_FOR;
END_IF;
// 理论上Found必为TRUE,因为Rising非0
IF #TempFound THEN
// 5. 计算下一次起始位:当前选中位之后的下一个使能位(基于Cfg,循环)
#TempNextFound := FALSE;
// 先找更高位
FOR #TempNextIndex := (#FirstTriggerID ) TO 7 DO
IF (#CfgByte AND SHL(IN := BYTE#16#01, N := #TempNextIndex)) <> 0 THEN
#NextStart := #TempNextIndex;
#TempNextFound := TRUE;
EXIT;
END_IF;
END_FOR;
// 若未找到,再从0开始找比当前位低的使能位
IF NOT #TempNextFound THEN
FOR #TempNextIndex := 0 TO #FirstTriggerID - 1 DO
IF (#CfgByte AND SHL(IN := BYTE#16#01, N := #TempNextIndex)) <> 0 THEN
#NextStart := #TempNextIndex;
#TempNextFound := TRUE;
EXIT;
END_IF;
END_FOR;
END_IF;
// 若仍未找到,说明只有当前位一个使能位,则下一次起始位仍为当前位
IF NOT #TempNextFound THEN
#NextStart := #FirstTriggerID;
END_IF;
ELSE
// 异常情况,置255
#FirstTriggerID := 255;
END_IF;
END_IF;
END_FUNCTION_BLOCK
FUNCTION "FC_TransferResult" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
TriggerID : Int;
Reset : Bool;
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
Statments : Array[1..8] OF Bool;
LastTriggerID : Int;
END_VAR
VAR_TEMP
END_VAR
VAR CONSTANT
END_VAR
BEGIN
IF #Reset THEN
#Statments[#LastTriggerID] := FALSE;
#LastTriggerID := 0;
ELSIF #TriggerID >= 1 AND #TriggerID <= 8 THEN
#Statments[#TriggerID] := true;
#LastTriggerID := #TriggerID;
END_IF;
END_FUNCTION
FUNCTION_BLOCK "FB_ArbiterNew"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
Mode : Int; // 模式
ResetBtn : Bool; // 复位按钮
ArbiterAllow : Bool; // 抢答启动按钮
QuizCmds : Byte; // 8个工位抢答按钮
EnableIDs : Byte; // 启用抢答的工位
InterlockNoSuccess : Byte;//无抢答成功
InterlockNoFoul : Byte;//无抢答犯规
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
Success : Array[1..8] OF Bool; // 成功标志
Fault : Array[1..8] OF Bool; // 犯规标志
Enable : Array[1..8] OF Bool;//抢答启用标志
END_VAR
VAR
StatFirstSuccessID : USInt;
StatLastFirstSuccessID : USInt;
StatDoubleLastFirstSuccessID : Int;
StatFirstFoulID : USInt;
StatLastFirstFoulID : USInt;
StatDoubleLastFirstFoulID : Int;
FB_DetectFirstQuiz_Instance {S7_SETPOINT := 'True'} : "FB_DetectFirstTrigger";
FB_DetectFirstFoul_Instance {S7_SETPOINT := 'True'} : "FB_DetectFirstTrigger";
END_VAR
VAR_TEMP
END_VAR
VAR CONSTANT
END_VAR
BEGIN
#FB_DetectFirstQuiz_Instance(Reset:=#ResetBtn,
ArbiterType:=#ArbiterAllow,
Interlock:=#InterlockNoFoul,
LampsNoTrue:=#InterlockNoSuccess,
DetectedByte:=#QuizCmds,
CfgByte:=#EnableIDs,
FirstTriggerID:=#StatFirstSuccessID,
LastFirstTriggerID:=#StatLastFirstSuccessID);
"FC_TransferResult"(TriggerID:=#StatLastFirstSuccessID,
Reset:=#ResetBtn,
Statments:=#Success,
LastTriggerID:=#StatDoubleLastFirstSuccessID);
#FB_DetectFirstFoul_Instance(Reset:=#ResetBtn,
ArbiterType:=NOT #ArbiterAllow,
Interlock:=#InterlockNoSuccess,
LampsNoTrue:=#InterlockNoFoul,
DetectedByte:=#QuizCmds,
CfgByte:=#EnableIDs,
FirstTriggerID:=#StatFirstFoulID,
LastFirstTriggerID:=#StatLastFirstFoulID);
"FC_TransferResult"(TriggerID:=#StatLastFirstFoulID,
Reset:=#ResetBtn,
Statments:=#Fault,
LastTriggerID:=#StatDoubleLastFirstFoulID);
END_FUNCTION_BLOCK
乍一看,上面的图挺唬人的,可是,抢答成功的检测还可以说得过去,只有一个抢答成功绿灯亮;可是,抢答犯规的红灯为什么也只有一个亮,如果是“法不责众”的想法作怪,那么执行起来就是谁先被检测到犯规扣谁的分,可是,同样是犯规,为什么只检测出来一个,不应该是都被检测出来吗?
国际田径大赛的规则,自2010年以来,就是“零抢跑”,“一枪就下”,谁抢跑就取消谁的比赛资格,不管你是第几个抢跑的,只管你做没做,而不管你是第几个做。
照此看,所有抢答犯规的人,都应该有被扣分的待遇。
所以,我的副标题是“系列程序设计”,也计划多盖几楼。