(* ============================================================ *)
(* CRC16校验 —— Modbus RTU 标准 CRC16（0xA001，初值 0xFFFF）    *)
(* S7-1200 TIA Portal SCL 源文件（导入用，GB2312/ANSI 编码）    *)
(* 导入：项目树->外部源文件->添加新外部源文件->从源生成块       *)
(* modbus_自由口从站 与 modbus_自由口主站共用的辅助 FC。        *)
(* 导入顺序：先导本文件生成 FC，再导两个 FB 源文件。            *)
(* 一个工程里只导一次。完整说明见 UTF-8 主版本。                *)
(* 接口：校验字节数 / 校验数据[0..255] / CRC低 / CRC高。        *)
(* 版本：v0.2  2026-09-29                                       *)
(* ============================================================ *)

FUNCTION "CRC16校验" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.2
   VAR_INPUT 
      校验字节数 : UInt;
   END_VAR

   VAR_IN_OUT 
      校验数据 : Array[0..255] of Byte;
   END_VAR

   VAR_OUTPUT 
      CRC低 : Byte;
      CRC高 : Byte;
   END_VAR

   VAR_TEMP 
      字节循环 : UInt;
      位循环 : UInt;
      CRC : UInt;
   END_VAR


BEGIN
	(* ============================================================ *)
	(* Modbus RTU 标准 CRC16：多项式 0xA001（0x8005 位反射），初值  *)
	(* 0xFFFF，逐字节、逐位右移。这是 Modbus 规范原文给的算法，     *)
	(* 不要换成"查表法"以外的其他变体（CRC-16/IBM 之外的初值/反射   *)
	(* 约定都会算出对不上的结果）。                                 *)
	(* ------------------------------------------------------------ *)
	(* 与原版（写回数组）的两点区别：                               *)
	(* 1. 不再修改调用方传进来的数组，改成用 CRC低/CRC高 两个输出   *)
	(* 参数把结果交出去。校验收到的帧时就不必再复制一份报文，       *)
	(* 省 256 字节局部数据；也不会把接收缓冲改花。                  *)
	(* 2. Modbus 在线路上是"CRC 低字节先发"，所以这里明确拆成两个   *)
	(* 字节，由调用方按 低->高 的顺序摆到报文末尾。                 *)
	(* ============================================================ *)
	#CRC := 16#FFFF;
	FOR #字节循环 := 1 TO #校验字节数 DO
	    #CRC := #CRC XOR #校验数据[#字节循环 - 1];
	    FOR #位循环 := 1 TO 8 DO
	        IF ((#CRC & 2#1) <> 1) THEN
	            #CRC := SHR(IN := #CRC, N := 1);
	        ELSE
	            #CRC := SHR(IN := #CRC, N := 1) XOR 16#A001;
	        END_IF;
	    END_FOR;
	END_FOR;
	#CRC低 := #CRC & 16#00FF;
	#CRC高 := SWAP(#CRC) & 16#00FF;
END_FUNCTION
