(* @NESTEDCOMMENTS := 'Yes' *) (* @PATH := '' *) (* @OBJECTFLAGS := '0, 8' *) (* @SYMFILEFLAGS := '2048' *) FUNCTION_BLOCK FB_INC_DECODER VAR_INPUT IN_xChA : BOOL; (* Channel A *) IN_xChB : BOOL; (* Channel B, 90-degree rotated to Channel A *) IN_xReset : BOOL; (* Reset counter to 0 *) IN_xEnPosWindow : BOOL; (* TRUE if you want to overflow the counting at min/max values below *) IN_diMinVal : DINT; IN_diMaxVal : DINT; END_VAR VAR_OUTPUT OUT_diActVal : DINT; END_VAR VAR_OUTPUT PERSISTENT OUT_pdiActVal : DINT; END_VAR VAR rtChA : R_TRIG; rtChB : R_TRIG; ftChA : F_TRIG; ftChB : F_TRIG; xFirstCycle : BOOL; END_VAR (* Incremental Encoder Decoder FB Author: mo Date: 2019-02 alternative OSCAT implementation (faulty!) below: axb := cha XOR chb; (* create pulses for channel a *) clka := cha XOR edgea; edgea := cha; clkb := chb XOR edgeb; edgeb := chb; (* create pulses for both channels *) clk := clka OR clkb; (* set the direction output *) IF axb AND clka THEN dir := TRUE; END_IF; IF axb AND clkb THEN dir := FALSE; END_IF; (* increment or decrement the counter *) IF clk AND dir THEN cnt := cnt + 1; END_IF; IF clk AND NOT dir THEN cnt := cnt -1; END_IF; (* reset the counter if rst active *) IF rst THEN cnt := 0; END_IF; => problem here is: if your first pulse is e.g. a rising edge on CHb with a high signal on CHa, it will increment cnt, but in the FALSE direction instead of the TRUE direction. so your first pulse might count into the wrong direction! This implementation accounts for all possible transitions. You can use OUT_diActVal or the persistent Version OUT_pdiActVal if your hardware is not moving while powered off to avoid reference runs at each restart of the PLC. 1 |----| |----| |- A 0 _| |____| |____| 1 |----| |----| |- B 0 _| |____| |____| *) (* @END_DECLARATION := '0' *) rtChA(clk:=IN_xChA); rtChB(clk:=IN_xChB); ftChA(clk:=IN_xChA); ftChB(clk:=IN_xChB); (* don't evaluate the changing edges when PLC starts up *) IF xFirstCycle THEN IF (rtChA.Q AND NOT IN_xChB) OR (ftChA.Q AND IN_xChB) OR (rtChB.Q AND IN_xChA) OR (ftChB.Q AND NOT IN_xChA) THEN OUT_diActVal := OUT_diActVal + 1; OUT_pdiActVal := OUT_pdiActVal + 1; END_IF IF (rtChA.Q AND IN_xChB) OR (ftChA.Q AND NOT IN_xChB) OR (rtChB.Q AND NOT IN_xChA) OR (ftChB.Q AND IN_xChA) THEN OUT_diActVal := OUT_diActVal - 1; OUT_pdiActVal := OUT_pdiActVal - 1; END_IF END_IF IF IN_xReset THEN OUT_diActVal := 0; OUT_pdiActVal := 0; END_IF IF IN_xEnPosWindow THEN IF OUT_diActVal < IN_diMinVal THEN OUT_diActVal := IN_diMaxVal - 1; END_IF IF OUT_diActVal >= IN_diMaxVal THEN OUT_diActVal := IN_diMinVal; END_IF (* for persistent counter *) IF OUT_pdiActVal < IN_diMinVal THEN OUT_pdiActVal := IN_diMaxVal - 1; END_IF IF OUT_pdiActVal >= IN_diMaxVal THEN OUT_pdiActVal := IN_diMinVal; END_IF END_IF xFirstCycle := TRUE; END_FUNCTION_BLOCK