ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 아두이노와 자바의 시리얼 통신(RFID)
    Arduino/Java Serial통신 2020. 12. 1. 22:17

     

    아래의 내용을 기반으로 만들도록 한다.

    designatedroom87.tistory.com/316?category=903927

     

    아두이노와 자바의 시리얼 통신(아두이노에서 자바로 0과 1을 전송)

    자바에서 시리얼 통신을 설정하지 않았다면 아래의 글을 참고하자. designatedroom87.tistory.com/315 Java 시리얼 통신 기본 설정 아래의 사이트에서 다운을 받는다. rxtx.qbang.org/wiki/index.php/Download#x64..

    designatedroom87.tistory.com

     

    RFID와 통신하기 전에 아래의 내용을 보도록 하자.

    아두이노의 소스 파일

    더보기
    void setup() {
      // put your setup code here, to run once:
      Serial.begin(9600);
    }
    
    int data = 0;
    
    void loop() {
      // put your main code here, to run repeatedly:
      Serial.write(data++);
      
      delay(2000);
    }

     

     

    기존의 SerialRead.java에 함수 2개를 추가한다.

    System.out.println(s); 대신에 System.out.println(SerialRead.stringToHex(s)); 를 호출한다.

    SerialRead.java

    더보기
    import java.io.*;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.nio.ByteBuffer;
    
    /*
    	아두이노가 데이터(0~9) 전송하고 자바가 이를 수신해서
    	웹 상으로 전송하려는 것
    	
    	자바에서 http URL 요청으로 웹에 보낸다.
    	보낼 data가 9라고 한다면 
    	http://localhost.9090/serialdata?data=9로 실어서 보낸다
    	데이터를 보낼 때, 위의 URL을 보내서 data값만 받아서 처리하면
    	가장 쉬울 듯하다.
    	
    	SerialRead 클래스가 데이터를 읽어오는 클래스이다.
    	
    	서버를 실행하면 "접속" 이라고 뜬다.
    */
    
    //	값을 읽는 클래스로, 이는 Thread로 구현해야 한다.
    public class SerialRead implements Runnable
    {
    	InputStream in;
    	
    	public SerialRead(InputStream in){this.in = in;}
    	
    	@Override
    	public void run() 
    	{
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		
    		try 
    		{
    			//	buffer에 저장하고나서, 그 길이를 반환한다.
    			while ((len = this.in.read(buffer)) > -1)
    			{
    				//	System.out.println(new String(buffer,0,len));
    				//	new DataProc(new String(buffer,0,len));
    				String s = new String(buffer,0,len);
    				
    				/*
    				if (len != 0) 
    					new DataProc(s);
    				*/
    				HttpURLConnection conn = null;
    				
    				//	들어온 데이터가 있는 경우에만 동작을 한다.
    				if (len != 0) 
    				{
    					//	데이터를 url 형태로 변형시킨다. s가 데이터이다.
    					//	System.out.println(s);
    					System.out.println(SerialRead.stringToHex(s));
    					
    					String targetURL = "http://localhost:9090/serialdata?data="+s;
    					
    					System.out.println(targetURL);
    			
    					URL url = new URL(targetURL);	//	URL은 String클래스와 비슷하나, 파싱까지 해준다.
    					
    					try 
    					{
    						//	http에 접속
    						conn = (HttpURLConnection) url.openConnection();	//	소켓을 열겠다는 요청
    						
    						//	스트림을 통해 데이터를 받는다.
    						BufferedReader br = new BufferedReader(
    								new InputStreamReader(conn.getInputStream()));
    						
    						String temp = null;
    						
    						while ((temp = br.readLine()) != null) {}
    						
    						br.close();
    						conn.disconnect();
    					}
    					catch(Exception e) {}
    				}
    			}
    		} 
    		catch (IOException e) {e.printStackTrace();}
    	}
    	
    	//	문자열을 16진수로 변환하는 함수
    	public static String stringToHex(String s) 
    	{
    	    String result = "";
    
    	    for (int i = 0; i < s.length(); i++) {
    	      result += String.format("%02X ", (int) s.charAt(i));
    	    }
    
    	    return result;
    	}
    	public static String stringToHex0x(String s) 
    	{
    	    String result = "";
    
    	    for (int i = 0; i < s.length(); i++) {
    	      result += String.format("0x%02X ", (int) s.charAt(i));
    	    }
    
    	    return result;
    	 }
    }

     

    Main.java를 실행시키고 1부터 값이 출력되는지 확인하자.

    아래는 콘솔 창의 출력 결과이다.

     

     

    아래에서 RFID에 대한 내용을 보고 오는 것도 좋은 방법이다.

    designatedroom87.tistory.com/312?category=903059

     

    RFID와 아두이노 연결 및 카드 정보 읽고 서보 모터 회전시키기

    라이브러리 관리에서 RFID검색하고 MFRC522의 깃허브커뮤니티로 설치한다. 하드웨어의 연결은 아래와 같다. RFID의 SDA는  아두이노의 10번핀에 연결 RFID의 SCK는     아두이노의 13번핀에 연결 RFID

    designatedroom87.tistory.com

    아두이노에서의 소스파일은 아래와 같다.

    아스키 코드 값을 참고하자.

    기존의 내용에서 달라진 점은 데이터를 한 번에 4개를 전송한다. (배열로 전송)

    아래의 아두이노 소스 파일에는 문제가 있다.

    이에 대한 개선 사항은 아래에 있으니, 보고 지나가도록 하자.

    아두이노 소스 파일

    더보기
    #include <MFRC522.h>
    #include <SPI.h>
    
    #define   SS_PIN  10  //  칩셋핀(데이터 핀)
    #define   RST_PIN 9   //  리셋핀
    MFRC522 mfrc522(SS_PIN, RST_PIN);
    
    void setup() {
      // put your setup code here, to run once:
      Serial.begin(9600);
      SPI.begin();            // Init SPI bus
      
      mfrc522.PCD_Init();     // Init mfrc522
    }
    
    void loop() {
      //  카드가 읽혀지지 않은 상태(찍히지 않으면)이면 리턴
      if (!mfrc522.PICC_IsNewCardPresent())   return;
      if (!mfrc522.PICC_ReadCardSerial())     return;
    
      //  uid카드 정보를 자바로 보낸다. uidByte는 배열이다.
      //Serial.write(mfrc522.uid.uidByte,4);  //  4는 카드 정보의 자리 수이다.
      printHex(mfrc522.uid.uidByte,4);
      
      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1();
    }
    
    void printHex(byte* buffer, byte bufferSize)
    {
      for (byte i = 0; i < bufferSize; i++)
      {
        // 0x10은 개행문자  
        buffer[i] < 0x10 ? " 0" : " ";
      }
      Serial.write(buffer, bufferSize);
    }

     

    SerialRead.java를 수정하자.

    아두이노에서 카드의 정보는 4자리임을 알 수 있다.

    이를 이용해서 SerialRead.java 를 수정하자.

    카드에서 넘어오는 정보들은 기존의 String인 s에 누적으로 저장을 한다.

    그리고 4개의 데이터를 받으면 이를 처리하면 된다.

    아래의 "FFFD FFFD FFFD 4E"는 흰색 카드에 대한 정보이다.

    아래의 카드는 아두이노의 흰색 카드에 대한 정보인데, 아두이노에서 읽은 데이터와 차이가 있다.

    흰색 카드를 자바에서 읽은 결과가 아래와 같은 값이므로 이를 흰색 카드의 정보로 받아들였다. 

    SerialRead.java

    더보기
    import java.io.*;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.nio.ByteBuffer;
    
    /*
    	아두이노가 데이터(0~9) 전송하고 자바가 이를 수신해서
    	웹 상으로 전송하려는 것
    	
    	자바에서 http URL 요청으로 웹에 보낸다.
    	보낼 data가 9라고 한다면 
    	http://localhost.9090/serialdata?data=9로 실어서 보낸다
    	데이터를 보낼 때, 위의 URL을 보내서 data값만 받아서 처리하면
    	가장 쉬울 듯하다.
    	
    	SerialRead 클래스가 데이터를 읽어오는 클래스이다.
    	
    	서버를 실행하면 "접속" 이라고 뜬다.
    */
    
    //	값을 읽는 클래스로, 이는 Thread로 구현해야 한다.
    public class SerialRead implements Runnable
    {
    	InputStream in;
    	
    	public SerialRead(InputStream in){this.in = in;}
    	
    	@Override
    	public void run() 
    	{
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		String data = "0";
    		
    		try 
    		{
    			String s = "";
    			//	buffer에 저장하고나서, 그 길이를 반환한다.
    			while ((len = this.in.read(buffer)) > -1)
    			{
    				//	System.out.println(new String(buffer,0,len));
    				//	new DataProc(new String(buffer,0,len));
    				//	String s = new String(buffer,0,len);
    				s += new String(buffer,0,len);
    				
    				/*
    				if (len != 0) 
    					new DataProc(s);
    				*/
    				HttpURLConnection conn = null;
    				
    				//	들어온 데이터가 있는 경우에만 동작을 한다.
    				//	4자리 일 때만 데이터를 받는다.
    				if (len != 0 && s.length() > 3) 
    				{
    					//	데이터를 url 형태로 변형시킨다. s가 데이터이다.
    					//	System.out.println(s);
    					System.out.println(SerialRead.stringToHex(s));
    					if (SerialRead.stringToHex(s).trim().equals("FFFD FFFD FFFD 4E"))
    					{
    						System.out.println("카드 일치");
    						data = "1";
    					}
    					else
    					{
    						System.out.println("카드 불일치");
    						data = "0";
    					}
    					s = "";
    					
    					String targetURL = "http://localhost:9090/serialdata?data="+data;
    					
    					System.out.println(targetURL);
    			
    					URL url = new URL(targetURL);	//	URL은 String클래스와 비슷하나, 파싱까지 해준다.
    					
    					try 
    					{
    						//	http에 접속
    						conn = (HttpURLConnection) url.openConnection();	//	소켓을 열겠다는 요청
    						
    						//	스트림을 통해 데이터를 받는다.
    						BufferedReader br = new BufferedReader(
    								new InputStreamReader(conn.getInputStream()));
    						
    						String temp = null;
    						
    						while ((temp = br.readLine()) != null) {}
    						
    						br.close();
    						conn.disconnect();
    					}
    					catch(Exception e) {}
    				}
    			}
    		} 
    		catch (IOException e) {e.printStackTrace();}
    	}
    	
    	//	문자열을 16진수로 변환하는 함수
    	public static String stringToHex(String s) 
    	{
    	    String result = "";
    
    	    for (int i = 0; i < s.length(); i++) {
    	      result += String.format("%02X ", (int) s.charAt(i));
    	    }
    
    	    return result;
    	}
    	public static String stringToHex0x(String s) 
    	{
    	    String result = "";
    
    	    for (int i = 0; i < s.length(); i++) {
    	      result += String.format("0x%02X ", (int) s.charAt(i));
    	    }
    
    	    return result;
    	 }
    }

     

    Main.java를 실행해보자.

    그리고 카드를 대면 다음과 같이 콘솔 창에 출력된다.

     

     

     

     

     

    위의 방식에는 문제점이 있다.

    개선방법은 아두이노에서 RFID의 값은 총 4개의 데이터이다.

    이 데이터들을 각 하나씩 16진수 2개로 쪼개서 전송하면 된다.

    그러면 총 8개의 데이터를 전송하는 것이다.

    얘를 들면, RFID의 입력값으로 0xfe, 0xff, 0xee, 0xaa 이렇게 4개의 데이터가 존재하면

    이 값들은 정수로 254, 255, 238, 170 표현된다.

    254를 16진수로 표현하면 15와 14가 되는데, 이를 FE로 나타내는 것이다.

    정확하게는 문자인 'F', 'E'로 표현한다.

    만일, 0x09와 같은 경우에는 십진수로 9이므로 이는 문자인 '9'로 표현해야 한다.

    그렇기 때문에 RFID의 각 데이터들은 2개씩 쪼개져야 한다.

    아래는 개선한 아두이노 소스 파일이다.

     

    아두이노 소스 내용

    더보기
    //               254,   255, 238   170
    //byte a[4] = { 0xfe, 0xff, 0xee, 0xaa };
    byte a[4] = { 0xfe, 0xff, 0x2e, 0x09 };
    
    void setup() {
      Serial.begin(9600);
    }
    
    void loop() {
      
      ShowOtherPrintHex(a, 4);
      
      delay( 2000 );  //  1000으로 두면, 데이터가 2번 읽히기 때문에, 2000으로 설정
    }
    
    /*  
      정수를 16진수로 변경하는 함수 
      9보다 크면, 'A' 에서 'F'를 반환하고, 9이하 이면, '0' ~ '9'를 반환하면 된다.
    */
    byte GetDecimalToHex(int decimal)
    {
      const int diffHex = 'A' - 10;   //  정수가 9를 초과할 때, 필요한 상수
      const int diffDecimal = '0';    //  정수가 9이하 일 때, 필요한 상수
      byte ret;
    
      /*  
        아스키 코드 값을 알아보자.
        'A'는 97의 값을 갖는다. 'F'는 102의 값을 갖는다.  
        '0'은 80의 값을 갖는다. '9'는 89의 값을 갖는다.
      */
      
      if ( decimal > 9 )  ret = diffHex + decimal;
      else                ret = diffDecimal + decimal;
    
      return ret;
    } 
    
    /*  RFID의 값을 16진수로 변경하고 송신하는 함수 */
    void ShowOtherPrintHex(byte* buffer, byte bufferSize)
    {
      byte l_buf[8] = { 0, }; //  카드는 4자리 수이지만, 각 자리수는 2자리로 나뉘므로 8칸의 공간 필요
      int l_bufLen = 0;       
      
      const int l_HEX = 16;
    
      //  각 16진수에 대응하는 수들을 16진수에 대응 문자들로 맵핑을 해야 한다.
      for (byte i = 0; i < bufferSize; i++)
      {
        //  a[0]에는 0xfe가 들어있는데, 이는 254의 값이다.
        //  254를 l_HEX로 나눈 몫과 나머지는 15와 14이다.
        //  15와 14는 9보다 크므로 'F'와 'D'로 변환해야 한다.
        int frontNum = buffer[i] / l_HEX;
        int rearNum = buffer[i] % l_HEX;
    
        //  카드의 각 자리 수마다 2자리 수로 나뉘므로, 이를 저장한다.
        l_buf[l_bufLen++] = GetDecimalToHex(frontNum);
        l_buf[l_bufLen++] = GetDecimalToHex(rearNum);
      }
      Serial.write(l_buf, 8);
    }

     

    자바 프로젝트를 새로 만들어서 진행해보자.

    아래에 전체 프로젝트가 들어있으므로, 이를 그대로 가져와서 써도 된다.

    새로 만들면 시리얼 통신 설정 및 RXTXcomm.jar파일과 rxtxSerial.dll 파일에 대한 설정도 다시 해줘야 한다.

    src폴더에 serial패키지를 하나 만들자.

    위에서 필요한 파일은 3개면 된다. Serial.java, Main.java, SerialRead.java 이렇게 3개만 있으면 된다.

    변경된 부분은 SerialRead.java만 변경되었다.

    Main.java
    0.00MB
    Serial.java
    0.00MB

     

    그리고 Serial.java를 보자.

    Serial.java에서 변경된 부분은 아래의 조건문이다.

    기존에는 4개의 RFID값을 받기위해 데이터의 길이가 3을 초과여야 했으나,

    아두이노에서 8개의 데이터를 전송하기 때문에, 데이터의 길이가 7을 초과해야 한다.

     

    아래는 run함수의 내용이다.

     

    SerialRead.java 파일내용

    더보기
    package serial;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class SerialRead implements Runnable{
    	InputStream in;
    	public SerialRead(InputStream in){	this.in = in;	}
    
    	@Override
    	public void run() {
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		String data = "0";
    		
    		try {
    			String s = "";
    			
    			while ((len = this.in.read(buffer)) > -1){
    				s += new String(buffer,0,len);
    				
    				HttpURLConnection conn = null;
    				
    				//	들어온 데이터가 있는 경우에만 동작을 한다.
    				//	데이터가 8자리일 때 받는다.
    				if (len != 0 && s.length() > 7) {
    					System.out.println("수신 데이터 : "+s);
    					
    					s = "";
    					String targetURL = "http://localhost:9090/serialdata?data="+data;
    					System.out.println(targetURL);
    					URL url = new URL(targetURL);
    					
    					try {
    						conn = (HttpURLConnection) url.openConnection();
    						
    						BufferedReader br = new BufferedReader(
    								new InputStreamReader(conn.getInputStream()));
    						
    						String temp = null;
    						
    						while ((temp = br.readLine()) != null) {}
    						
    						br.close();
    						conn.disconnect();
    					}catch(Exception e) {}
    				}
    			}
    		} catch (IOException e) {e.printStackTrace();}
    	}
    }

     

    아두이노에 소스 파일을 업로드하고, Main.java의 main함수를 실행해보자.

    2초 간격으로 아두이노의 0xfe, 0xff, 0x2e, 0x09 값을 16진수로 수신받음을 알 수 있다.

     

     

     

    위의 내용을 토대로 아두이노에서 RFID값을 입력받아 자바로 시리얼 통신을 해보자.

    아래는 아두이노의 소스 파일이다.

    위의 소스에서 RFID에 대한 내용만 추가하면 된다.

     

    아두이노 소스 파일

    더보기
    //  흰 카드     97   A8 B6 4E
    //  파란색 카드  C7  99  FF  32
    
    #include <MFRC522.h>
    #include <SPI.h>
    
    #define   SS_PIN  10  //  칩셋핀(데이터 핀)
    #define   RST_PIN 9   //  리셋핀
    MFRC522 mfrc522(SS_PIN, RST_PIN);
    
    void setup() {
      Serial.begin(9600);
      SPI.begin();            // Init SPI bus
      
      mfrc522.PCD_Init();     // Init mfrc522
    }
    
    void loop() {
      //  카드가 읽혀지지 않은 상태(찍히지 않으면)이면 리턴
      if (!mfrc522.PICC_IsNewCardPresent())   return;
      if (!mfrc522.PICC_ReadCardSerial())     return;
    
      ShowOtherPrintHex( mfrc522.uid.uidByte, 4 );
      
      mfrc522.PICC_HaltA();
      mfrc522.PCD_StopCrypto1(); 
    }
    
    byte GetDecimalToHex(int decimal)
    {
      const int diffHex = 'A' - 10;   
      const int diffDecimal = '0';   
      byte ret;
      
      if ( decimal > 9 )  ret = diffHex + decimal;
      else                ret = diffDecimal + decimal;
    
      return ret;
    } 
    
    void ShowOtherPrintHex(byte* buffer, byte bufferSize)
    {
      byte l_buf[8] = { 0, };
      int l_bufLen = 0;       
      
      const int l_HEX = 16;
    
      for (byte i = 0; i < bufferSize; i++)
      {
        int frontNum = buffer[i] / l_HEX;
        int rearNum = buffer[i] % l_HEX;
    
        l_buf[l_bufLen++] = GetDecimalToHex(frontNum);
        l_buf[l_bufLen++] = GetDecimalToHex(rearNum);
      }
      Serial.write(l_buf, 8);
    }

     

    SerialRead.java에만 변경사항이 있다.

    아래는 run메소드의 try문이다.

     

    SerialRead.java 파일 내용

    더보기
    package serial;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class SerialRead implements Runnable{
    	InputStream in;
    	public SerialRead(InputStream in){	this.in = in;	}
    
    	@Override
    	public void run() {
    		byte[] buffer = new byte[1024];
    		int len = -1;
    		String data = "0";
    		
    		try {
    			String s = "";
    			
    			while ((len = this.in.read(buffer)) > -1){
    				s += new String(buffer,0,len);
    				
    				HttpURLConnection conn = null;
    				
    				//	들어온 데이터가 있는 경우에만 동작을 한다.
    				//	데이터가 8자리일 때 받는다.
    				if (len != 0 && s.length() > 7) {
    					System.out.println("수신 데이터 : "+s);
    					
    					if (s.trim().equals("97A8B64E"))
    					{
    						System.out.println("흰색 카드 태그");
    						data = "1";
    					}
    					else if (s.trim().equals("C799FF32"))
    					{
    						System.out.println("파란 카드  태그");
    						data = "1";
    					}
    					else
    					{
    						System.out.println("등록되지 않은 카드");
    						data = "0";
    					}
    					
    					s = "";
    					String targetURL = "http://localhost:9090/serialdata?data="+data;
    					System.out.println(targetURL);
    					URL url = new URL(targetURL);
    					
    					try {
    						conn = (HttpURLConnection) url.openConnection();
    						
    						BufferedReader br = new BufferedReader(
    								new InputStreamReader(conn.getInputStream()));
    						
    						String temp = null;
    						
    						while ((temp = br.readLine()) != null) {}
    						
    						br.close();
    						conn.disconnect();
    					}catch(Exception e) {}
    				}
    			}
    		} catch (IOException e) {e.printStackTrace();}
    	}
    }

     

    아두이노에 업로드를 하고, Main.java에서 실행을 하고 RFID의 값을 읽어보자.

    흰 카드     97 A8 B6 4E

    파란색 카드 C7 99 FF 32

    각 값들이 제대로 들어오는 것을 볼 수 있다.

     

    프로젝트 폴더

    RFID.zip
    0.18MB

     

     

     

     

    마지막으로 궁금한것이 생겼다.

    SerialRead.java를 다음과 같이 원래의 기본 소스를 약간 변경해서 실행시켜 RFID의 값을 읽어보자.

     

    흰색 카드를 읽었을 때의 실행결과이다.

    위의 순수 실행결과 하나만을 가지고 이야기해보자.

    버퍼에 있는 흰색카드정보를 한 개씩 가지고 올 경우도 있고, 3개씩 가지고 오는 경우도 있다.

    그리고 변수 cnt를 하나 두어서 다음을 실험해보자.

     

    아래는 그 실행결과이다.

    아래의 결과를 가지고 다음을 유추할 수 있다.

    버퍼에서 데이터를 모두 읽어들일 때까지 while 루프에 머물고 있다.

     

    여기서, 한 가지 생각해낸 것이 버퍼에서 데이터를 모두 가지고 온 후에

    이 데이터가 어떤 것인지 판단하는 것이 좋지 않을까 한다. 

    그 이유는 이 데이터가 꼭 RFID 카드값이 아닌, 버튼값일 수도 있다.

    그러므로 먼저 버퍼에 들어온 데이터를 그대로 저장하도록 만들어 보자.

    그러기 위해서는 멤버 변수가 3개 필요하다.

     

    그리고 run메소드를 보자.

     

    위의 run메소드에서 아두이노로부터 받은 데이터를 받아서 어떻게 처리할지는 아래의 else if문장에서 처리하면 된다.

    데이터를 웹으로 넘기든지 단순히 출력만 할 것인지를 판단해서 프로그래밍하면 되겠다.

     

    그리고 아래의 실행결과를 보자.

    흰색 카드와 파란 카드가 모두 잘 출력됨을 볼 수 있다.

     

    댓글

Designed by Tistory.