본문 바로가기
프로그램/JAVA

[ JAVA ] 자바로 구현해보는 모드버스의 crc16 체크섬

by 구서기 2013. 6. 26.

 

C로 구현해본데 이어 이번엔 java로 구현해 보았다.

추가로 시리얼모드외에 TCP모드의 프로토콜 생성함수도 포함 ^^.

[Java Souce]

 

package hclass.solra;

import hclass.util.HtypeCast;

/**
 * MODBUS 통신 프로토콜
 * 작정자명: HwangKyuseok(황규석)
 * 작성일자: 2013.06.011 ~ 06. 18
 * 비    고: 	
 * 		1. Modbus RTU (바이너리) 통신: CRC16 체크섬 생성, 주소생성모듈(시리얼용, TCP/IP용)		 
 *		
 *		2. 2013.06.18 완료: 현재 펑션코드 0x03 Read Multipe Registers  			 
 *		 (워드 단위 입/출력포트 상태읽기) 의  주소 체계만 진행됨
 * */

public class Modbus extends HtypeCast {
	int[] CRC = new int[256];
	/** **********************************************************************************
	 * CRC16 테이블초기화 (modbus RTU 시리얼통신에 사용)
	 * 
	 * 생성자 함수
	 * **********************************************************************************/
	public Modbus()
	{
		CRC[0] =   0X0000;	CRC[1] =   0XC0C1;	CRC[2] =   0XC181;	CRC[3] =   0X0140;	CRC[4] =   0XC301;	CRC[5] =   0X03C0;	CRC[6] =   0X0280;	CRC[7] =   0XC241;
		CRC[8] =   0XC601;	CRC[9] =   0X06C0;	CRC[10] =  0X0780;	CRC[11] =  0XC741;	CRC[12] =  0X0500;	CRC[13] =  0XC5C1;	CRC[14] =  0XC481;	CRC[15] =  0X0440;
		CRC[16] =  0XCC01;	CRC[17] =  0X0CC0;	CRC[18] =  0X0D80;	CRC[19] =  0XCD41;	CRC[20] =  0X0F00;	CRC[21] =  0XCFC1; 	CRC[22] =  0XCE81;	CRC[23] =  0X0E40;
		CRC[24] =  0X0A00;  CRC[25] =  0XCAC1; 	CRC[26] =  0XCB81;	CRC[27] =  0X0B40;	CRC[28] =  0XC901; 	CRC[29] =  0X09C0; 	CRC[30] =  0X0880;	CRC[31] =  0XC841;
		CRC[32] =  0XD801; 	CRC[33] =  0X18C0; 	CRC[34] =  0X1980; 	CRC[35] =  0XD941; 	CRC[36] =  0X1B00; 	CRC[37] =  0XDBC1; 	CRC[38] =  0XDA81; 	CRC[39] =  0X1A40;
		CRC[40] =  0X1E00; 	CRC[41] =  0XDEC1; 	CRC[42] =  0XDF81;	CRC[43] =  0X1F40;	CRC[44] =  0XDD01;	CRC[45] =  0X1DC0;	CRC[46] =  0X1C80;	CRC[47] =  0XDC41;
		CRC[48] =  0X1400;	CRC[49] =  0XD4C1;	CRC[50] =  0XD581;	CRC[51] =  0X1540;	CRC[52] =  0XD701;	CRC[53] =  0X17C0;	CRC[54] =  0X1680;	CRC[55] =  0XD641;
		CRC[56] =  0XD201;	CRC[57] =  0X12C0;	CRC[58] =  0X1380;	CRC[59] =  0XD341;	CRC[60] =  0X1100;	CRC[61] =  0XD1C1;	CRC[62] =  0XD081;	CRC[63] =  0X1040;
		CRC[64] =  0XF001;	CRC[65] =  0X30C0;	CRC[66] =  0X3180;	CRC[67] =  0XF141;	CRC[68] =  0X3300;	CRC[69] =  0XF3C1;	CRC[70] =  0XF281;	CRC[71] =  0X3240;
		CRC[72] =  0X3600;	CRC[73] =  0XF6C1;	CRC[74] =  0XF781;	CRC[75] =  0X3740;	CRC[76] =  0XF501;	CRC[77] =  0X35C0;	CRC[78] =  0X3480;	CRC[79] =  0XF441;
		CRC[80] =  0X3C00;	CRC[81] =  0XFCC1;	CRC[82] =  0XFD81;	CRC[83] =  0X3D40;	CRC[84] =  0XFF01;	CRC[85] =  0X3FC0;	CRC[86] =  0X3E80;	CRC[87] =  0XFE41;  
		CRC[88] =  0XFA01;	CRC[89] =  0X3AC0;	CRC[90] =  0X3B80;	CRC[91] =  0XFB41;	CRC[92] =  0X3900;	CRC[93] =  0XF9C1;	CRC[94] =  0XF881;	CRC[95] =  0X3840;
		CRC[96] =  0X2800;	CRC[97] =  0XE8C1;	CRC[98] =  0XE981;	CRC[99] =  0X2940;	CRC[100] = 0XEB01;	CRC[101] = 0X2BC0;	CRC[102] = 0X2A80;	CRC[103] = 0XEA41;
		CRC[104] = 0XEE01;	CRC[105] = 0X2EC0;	CRC[106] = 0X2F80;	CRC[107] = 0XEF41;	CRC[108] = 0X2D00;	CRC[109] = 0XEDC1;	CRC[110] = 0XEC81;	CRC[111] = 0X2C40;
		CRC[112] = 0XE401;	CRC[113] = 0X24C0;	CRC[114] = 0X2580;	CRC[115] = 0XE541;	CRC[116] = 0X2700;	CRC[117] = 0XE7C1;	CRC[118] = 0XE681;	CRC[119] = 0X2640;
		CRC[120] = 0X2200;	CRC[121] = 0XE2C1;	CRC[122] = 0XE381;	CRC[123] = 0X2340;	CRC[124] = 0XE101;	CRC[125] = 0X21C0;	CRC[126] = 0X2080;	CRC[127] = 0XE041;  
		CRC[128] = 0XA001;	CRC[129] = 0X60C0;	CRC[130] = 0X6180;	CRC[131] = 0XA141;	CRC[132] =  0X6300;	CRC[133] = 0XA3C1;	CRC[134] = 0XA281;	CRC[135] = 0X6240;
		CRC[136] = 0X6600;	CRC[137] = 0XA6C1;	CRC[138] = 0XA781;	CRC[139] = 0X6740;	CRC[140] =  0XA501;	CRC[141] = 0X65C0;	CRC[142] = 0X6480;	CRC[143] = 0XA441;
		CRC[144] = 0X6C00;	CRC[145] = 0XACC1;	CRC[146] = 0XAD81;	CRC[147] = 0X6D40;	CRC[148] =  0XAF01;	CRC[149] = 0X6FC0;	CRC[150] = 0X6E80;	CRC[151] = 0XAE41;
		CRC[152] = 0XAA01;	CRC[153] = 0X6AC0;	CRC[154] = 0X6B80;	CRC[155] = 0XAB41;	CRC[156] =  0X6900;	CRC[157] = 0XA9C1;	CRC[158] = 0XA881;	CRC[159] = 0X6840;
		CRC[160] = 0X7800;	CRC[161] = 0XB8C1;	CRC[162] = 0XB981;	CRC[163] = 0X7940;	CRC[164] =  0XBB01;	CRC[165] = 0X7BC0;	CRC[166] = 0X7A80;	CRC[167] = 0XBA41;
		CRC[168] = 0XBE01;	CRC[169] = 0X7EC0;	CRC[170] = 0X7F80;	CRC[172] = 0XBF41;	CRC[173] =  0X7D00;	CRC[174] = 0XBDC1;	CRC[175] = 0XBC81;	CRC[176] = 0X7C40;
		CRC[176] = 0XB401;	CRC[177] = 0X74C0;	CRC[178] = 0X7580;	CRC[179] = 0XB541;	CRC[180] =  0X7700;	CRC[181] = 0XB7C1;	CRC[182] = 0XB681;	CRC[183] = 0X7640;
		CRC[184] = 0X7200;	CRC[185] = 0XB2C1;	CRC[186] = 0XB381;	CRC[187] = 0X7340;	CRC[188] =  0XB101;	CRC[189] = 0X71C0;	CRC[190] = 0X7080;	CRC[191] = 0XB041;
		CRC[192] = 0X5000;	CRC[193] = 0X90C1;	CRC[194] = 0X9181;	CRC[195] = 0X5140;	CRC[196] =  0X9301;	CRC[197] = 0X53C0;	CRC[198] = 0X5280;	CRC[199] = 0X9241;
		CRC[200] = 0X9601;	CRC[201] = 0X56C0;	CRC[202] = 0X5780;	CRC[203] = 0X9741;	CRC[204] =  0X5500;	CRC[205] = 0X95C1;	CRC[206] = 0X9481;	CRC[207] = 0X5440;
		CRC[208] = 0X9C01;	CRC[209] = 0X5CC0;	CRC[210] = 0X5D80;	CRC[211] = 0X9D41;	CRC[212] =  0X5F00;	CRC[213] = 0X9FC1;	CRC[214] = 0X9E81;	CRC[215] = 0X5E40;
		CRC[216] = 0X5A00;	CRC[217] = 0X9AC1;	CRC[218] = 0X9B81;	CRC[219] = 0X5B40;	CRC[220] =  0X9901;	CRC[221] = 0X59C0;	CRC[222] = 0X5880;	CRC[223] = 0X9841;
		CRC[224] = 0X8801;	CRC[225] = 0X48C0;	CRC[226] = 0X4980;	CRC[227] = 0X8941;	CRC[228] =  0X4B00;	CRC[229] = 0X8BC1;	CRC[230] = 0X8A81;	CRC[231] = 0X4A40;
		CRC[232] = 0X4E00;	CRC[233] = 0X8EC1;	CRC[234] = 0X8F81;	CRC[235] = 0X4F40;	CRC[236] =  0X8D01;	CRC[237] = 0X4DC0;	CRC[238] = 0X4C80;	CRC[239] = 0X8C41;
		CRC[240] = 0X4400;	CRC[241] = 0X84C1;	CRC[242] = 0X8581;	CRC[243] = 0X4540;	CRC[244] =  0X8701;	CRC[245] = 0X47C0;	CRC[246] = 0X4680;	CRC[247] = 0X8641;
		CRC[248] = 0X8201;	CRC[249] = 0X42C0;	CRC[250] = 0X4380;	CRC[251] = 0X8341;	CRC[252] =  0X4100;	CRC[253] = 0X81C1;	CRC[254] = 0X8081;	CRC[255] = 0X4040;

	}
	
	/** *********************************************************************************
	 * 헥사값 배열의 CRC16 을 구하는 함수
	 * 
	 * 파라메터에 해당하는 값의 CRC16 체크섬값을 만들어 리턴
	 * @param	bytes: 프로토콜 6byte (일반적인 파라메터)
	 * @return	byte[2]형 16bit CRC값 리턴 
	 * *********************************************************************************/
	public byte[] fn_makeCRC16(byte[] bytes)
	{
		int icrc = 0xFFFF;
    	for (byte b : bytes) {
            icrc = (icrc >>> 8) ^ CRC[(icrc ^ b) & 0xff];
        }
		//System.out.println("Integer " + Integer.toHexString(icrc)); // test
		return fnShortToBytes((short)icrc,0);//HtypeCast의 short를 byte로 변환해주는 함수
	}
	/** *********************************************************************************
	 * 헥사값 배열의 CRC16 을 구하는 함수
	 * 
	 * 파라메터에 해당하는 값의 CRC16 체크섬값을 만들어 리턴
	 * @param	bytes: 프로토콜 6byte (일반적인 파라메터)
	 * @return	short형 16bit CRC값 리턴 
	 * *********************************************************************************/
	public short fn_makeCRC16toShort(byte[] bytes)
	{
		int icrc = 0xFFFF;
    	for (byte b : bytes) {
            icrc = (icrc >>> 8) ^ CRC[(icrc ^ b) & 0xff];
        }

		return (short)icrc;
	}
	/** *********************************************************************************
	 * 인자를 이용한 MODBUS 프로토콜 생성함수 (CRC가 있는 시리얼용)
	 * 
	 * @param	id:		장비번호 (모두 십진수)
	 * 			fnr:	펑션코드
	 * 			rst:	레지스터 시작주소
	 * 			rcnt:	레지스터 갯수
	 * 
	 * @return 8byte의 프로토콜 리턴 (CRC16포함) 
	 * *********************************************************************************/
	public byte[] fn_makePTcom(int id, int fnr, int rst, int rcnt )
	{
		byte[] btmp;
		byte[] bytes = new byte[6];
		byte[] rbyte = new byte[8];
		
		bytes[0] = (byte) id;	//장치번호
		bytes[1] = (byte) fnr;	//펑션코드
		
		if(fnr == 3)	rst = rst - 40001;
		
		//레지스터 시작주소
		btmp = fnShortToBytes((short)rst,1);
		bytes[2] = btmp[0];
		bytes[3] = btmp[1];
		
		//가져올 레지스터 갯수
		btmp = fnShortToBytes((short)rcnt,1);
		bytes[4] = btmp[0];
		bytes[5] = btmp[1];
		
		btmp = fn_makeCRC16(bytes);// CRC구함
		for(int i=0;i<6;i++) rbyte[i] = bytes[i];
		rbyte[6] = btmp[0];
		rbyte[7] = btmp[1];
		//System.out.println("CRC16 = " + Integer.toHexString(btmp[0])); //test
		return rbyte; 
	}
	/** *********************************************************************************
	 * 인자를 이용한 MODBUS 프로토콜 생성함수 (CRC가 없고 Header가 있는 TCP/IP용)
	 * 
	 * @param	id:		장비번호 (모두 십진수)
	 * 			fnr:	펑션코드
	 * 			rst:	레지스터 시작주소
	 * 			rcnt:	레지스터 갯수
	 * 
	 * @return 12byte의 프로토콜 리턴 (헤더포함) 
	 * *********************************************************************************/
	public byte[] fn_makePTsock(int id, int fnr, int rst, int rcnt )
	{
		byte[] btmp;
		byte[] rbyte = new byte[12];
		//HEADER
		//카운터
		rbyte[0] = 0x00; 
		rbyte[1] = 0x00;
		//프로토콜아이디 0x0000 고정
		rbyte[2] = 0x00; 
		rbyte[3] = 0x00;
		//길이
		rbyte[4] = 0x00; 
		rbyte[5] = 0x06;
		//
		rbyte[6] = (byte) id;	//유닛번호	
		rbyte[7] = (byte) fnr;	//펑션코드
		
		if(fnr == 3)	rst = rst - 40001;
		
		//레지스터 시작주소
		btmp = fnShortToBytes((short)rst,1);
		rbyte[8] = btmp[0];
		rbyte[9] = btmp[1];
		
		//가져올 레지스터 갯수
		btmp = fnShortToBytes((short)rcnt,1);
		rbyte[10] = btmp[0];
		rbyte[11] = btmp[1];
		return rbyte; 
	}
	/** *********************************************************************************
	 * [사용예]
	 * *********************************************************************************/
	public static void main(String[] args) {
		//byte[] bytes = {0x01,0x03,0x01,0x00,0x00,0x17};	//0x04 0x38
		byte[] bytes = {0x01,0x03,0x00,0x00,0x00,0x06};		
		Modbus t = new Modbus();
		
		
		System.out.println("[유닛번호:1, 펑션코드:3, 레지스터시작:40001, 레지스터갯수:6개 일때] ");
		System.out.println("Modbus프로토콜의 헥사코드는 아래와 같다.");
		byte r[] = t.fn_makePTcom(1,3,40001,6);
		for(byte b:r) {
			System.out.format("0x%02X ", b);
		}
		
		byte crc1[] = t.fn_makeCRC16(bytes);
		System.out.println("\n\n[CRC16 예]");
		System.out.format("byte[0] = %02X\n",crc1[0]);
		System.out.format("byte[1] = %02X\n",crc1[1]);
		
		short crc2 = t.fn_makeCRC16toShort(bytes);
		System.out.format("\nshort = %02X\n",crc2);
		 
	}

}

 

[실행결과화면]

[유닛번호:1, 펑션코드:3, 레지스터시작:40001, 레지스터갯수:6개 일때]
Modbus프로토콜의 헥사코드는 아래와 같다.
0x01 0x03 0x00 0x00 0x00 0x06 0xC5 0xC8

[CRC16 예]
byte[0] = C5
byte[1] = C8

short = C8C5