728x90

Android 에서 파일에 LogData를 기록할 목적으로 만든 코드이다.

 

public static void dataSaveLog(String _log, String _fileName) {
    /* SD CARD 하위에 LOG 폴더를 생성하기 위해 미리 dirPath에 생성 할 폴더명을 추가 */
    String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/LOG/";
    File file = new File(dirPath);
 
    // 일치하는 폴더가 없으면 생성
    if (!file.exists())
        file.mkdirs();
 
    // txt 파일 생성
    File savefile = new File(dirPath + "LOG_" + _fileName + ".txt");
    try {
        BufferedWriter bfw = new BufferedWriter(new FileWriter(dirPath + "LOG_" + _fileName + ".txt"true));
        bfw.write(_log);
        bfw.write("\n");
        bfw.flush();
        bfw.close();
    } catch (IOException e){
        /* Exception */
    }
}
 

 

Android 11 에서 USB 케이블 없이 Wi-Fi 로 연결하여 콘솔앱에서 출력되는 메시지를 확인할 수 있어서

지금은 위 코드를 굳이 사용할 필요는 없어졌다.

 

Android Studio 에서 ADB를 이용하여 LogCat 정보를 확인하는 방법

adb tcpip 5555

// 192.168.1.32 는 스마트폰이 연결된 Wi-Fi 주소

adb connect 192.168.1.32:5555

 

'안드로이드 > Android Serial 통신' 카테고리의 다른 글

Geateway 파싱 처리  (0) 2020.05.23
AlertDialog EditText  (0) 2019.06.26
Android 팝업창(popup window) 만들기  (0) 2019.06.01
android handler 를 이용한 지연처리  (0) 2019.05.09
Serial Communication Key  (0) 2019.05.09
블로그 이미지

Link2Me

,
728x90

특정 문자열을 파싱처리하는 걸 처리하는데 동일한 값이 두번 나온다고 한다.

그래서 두번째 값을 읽어낸다고 한다.

한번만 해당 정보가 출력되는 줄 알았는데 동일한 정보가 여러개 있을 수 있는 환경이다.


String gw_alloc;

private void parseDeviceIPAddress(String msg){
    String[] line = msg.split("\\n"); // 라인 단위로 메시지를 분리하라.
    for(int i=0; i < line.length;i++){
        if(line[i].contains("GW=")){  // GW address 추출
            String str = line[i].trim(); // 문자열 앞의 공백 제거 목적
            int stlocation = str.indexOf("GW=");
            int endlocation = str.length();
            String temp = str.substring(stlocation+3, endlocation-1).trim();
            int firstcomma = temp.indexOf(","); // 처음 나오는 , 를 찾아라.
            String gwaddress = temp.substring(0,firstcomma).trim();
            if(gwaddress.length() > 8){
                gw_alloc = gwaddress;
            }
        }
    }
}


그래서 첫번째 값을 읽어내도록 하는 걸 처리하기 위해서 코드를 아래와 같이 ArrayList를 이용해서 수정했다.

ArrayList<String> gw_alloc = new ArrayList<>();

private void parseDeviceIPAddress(String msg){
    String[] line = msg.split("\\n"); // 라인 단위로 메시지를 분리하라.
    for(int i=0; i < line.length;i++){
        if(line[i].contains("GW=")){  // GW address 추출
            String str = line[i].trim(); // 문자열 앞의 공백 제거 목적
            int stlocation = str.indexOf("GW=");
            int endlocation = str.length();
            String temp = str.substring(stlocation+3, endlocation).trim();
            int firstcomma = temp.indexOf(","); // 처음 나오는 , 를 찾아라.
            String gwaddress = temp.substring(0,firstcomma).trim();
            if(gwaddress.length() > 8){
                gw_alloc.add(gwaddress);
            }
        }
    }
}


해당 정보가 있으면 ArrayList 에 add하고 출력할 때는 gw_alloc.get(0); 를 해주면 첫번째 값을 활용할 수 있다.

블로그 이미지

Link2Me

,
728x90
안드로이드 팝업창에서 입력 정보를 받아서 처리하는 AlertDialog 코드 예시다.
IP 등록 함수 구현 내용은 적어두지 않는다.

AlertDialog.Builder alert_ipedit = new AlertDialog.Builder(MainActivity.this);
alert_ipedit.setTitle("XXX 스위치 IP주소 등록");
alert_ipedit.setMessage("(예시) 10.10.10.10/24");
final EditText etip = new EditText(MainActivity.this);
alert_ipedit.setView(etip);
if(ipsubnet != null){
    etip.setText(ipsubnet.trim()); // 한번 입력한 값이 있으면 표시한다.
}
// 확인 버튼 설정
alert_ipedit.setPositiveButton("등록", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        ipsubnet = etip.getText().toString().trim();
        if(ipsubnet.length() == 0) {
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("입력된 정보가 없습니다.").setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
            return;
        }
        String result = IPSubnetMask(ipsubnet);
        if(result.equals("1")){
            IPReg(ipsubnet); // IP 등록함수 실행
        } else if(result.equals("2")){
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("ip 주소 입력이 잘못되었습니다.\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        } else if(result.equals("3")){
            // 서브넷 마스크 맞는지 팝업창
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("서브넷 마스크가 맞습니까?\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            IPReg(ipsubnet); // IP 등록함수 실행
                        }
                    }).setNegativeButton("취소",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        } else {
            AlertDialog.Builder subnetmask_confirm = new AlertDialog.Builder(MainActivity.this);
            subnetmask_confirm.setMessage("IP 주소 입력 정보가 잘못되었습니다.\n입력값 :"+ipsubnet).setCancelable(false).setPositiveButton("확인",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) { // 'YES'
                            dialog.dismiss();
                        }
                    });
            AlertDialog alert1 = subnetmask_confirm.create();
            alert1.show();
        }
    }
});
alert_ipedit.setNegativeButton("취소", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
});
alert_ipedit.show();




블로그 이미지

Link2Me

,
728x90

콘솔 앱은 UI 전환없이 UI내에서 아이콘 배치를 효율적으로 해야 이용하는데 불편함이 적다.

화면 전환없이 정보를 보여주는 것을 처리하기 위해서 팝업 메뉴를 사용하고, 도움말 기능은 팝업 메뉴로는 해결할 수가 없어서 팝업 윈도우 기능을 이용하여 구현했다.


팝업 윈도우 기능은 활용하기에 따라 유용한 점이 많을 거 같다.

기본적으로 PopupWindow 자체적으로 show(), dismiss(), setFocusable() 등의 메서드를 제공해 준다.


popup_window.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linear_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffffff">

    <WebView
        android:id="@+id/popupwebView"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="#eee9e9" />

    <ImageButton
        android:id="@+id/btn_close"
        android:layout_width="31dp"
        android:layout_height="31dp"
        android:layout_marginTop="10dp"
        android:layout_marginRight="15dp"
        android:layout_gravity="right"
        android:background="@android:color/transparent"
        android:src="@drawable/btn_close" />

</LinearLayout>


버튼을 클릭하면 팝업창이 뜨는 구조이므로 View view 에서 동작한다.

HTML 파일을 bootstrap 기반으로 만들었고, 이걸 가장 잘 보여줄 수 있는 것은 WebView 라서 WebView 기능의 일부를 구현하여 팝업창이 구현되도록 했다.

URL 만 변경하면 다른 내용도 보여줄 수 있도록 함수화를 했다.

팝업창 외부 터치시 팝업창이 사리지도록 옵션을 설정했다.


case R.id.btnHelp:
   // 설정방법 설명
   mPopupWindowShow(view,Value.IPADDRESS + "/help.html");
   break;


public void mPopupWindowShow(View view, String url) {
    mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    Display display = mWindowManager.getDefaultDisplay();
    //int width = LinearLayout.LayoutParams.MATCH_PARENT; // 100% 너비를 사용할 때
    int width = (int) (display.getWidth() * 0.9); //Display 사이즈의 90%
    int height = LinearLayout.LayoutParams.WRAP_CONTENT;
    // LayoutParams WRAP_CONTENT를 주면 inflate된 View의 사이즈 만큼의 PopupWinidow를 생성한다.

    // inflate the layout of the popup window
    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.popup_window,null);
    mPopupView = layout.findViewById(R.id.linear_layout);

    WebView webView = (WebView) layout.findViewById(R.id.popupwebView);
    ImageButton btn_close = layout.findViewById(R.id.btn_close);

    // create the popup window
    boolean focusable = true; // lets taps outside the popup also dismiss it
    final PopupWindow popupWindow = new PopupWindow(mPopupView, width, height, focusable);

    WebSettings webSettings = webView.getSettings();
    webSettings.setDefaultFontSize(9);
    webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
    webView.loadUrl(url);
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    });

    popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0); // Parent View 로부터의 위치


    btn_close.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            popupWindow.dismiss();
        }
    });
}


 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
 Display display = mWindowManager.getDefaultDisplay();

 Point point = new Point();
 display.getSize(point);
 int width = (int) (point.x * 0.9); // Display 사이즈의 90%
 //int width = LinearLayout.LayoutParams.MATCH_PARENT;
 //int height = (int) (point.y * 0.8);
 int height = LinearLayout.LayoutParams.WRAP_CONTENT;
      

'안드로이드 > Android Serial 통신' 카테고리의 다른 글

Geateway 파싱 처리  (0) 2020.05.23
AlertDialog EditText  (0) 2019.06.26
android handler 를 이용한 지연처리  (0) 2019.05.09
Serial Communication Key  (0) 2019.05.09
Spinner 기능을 이용한 세팅 코드 구현  (0) 2019.05.02
블로그 이미지

Link2Me

,
728x90

delay를 주고 어떤 동작을 하고 싶다면 Handler클래스의 postDelayed 메소드를 이용한다.
Handler delayHandler = new Handler();
delayHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        //여기에 딜레이 후 시작할 작업들을 입력
    }
}, 3000); // 3초 지연을 준 후 시작



일정시간 지연후 처리 예제


final Handler delayHandler = new Handler();
delayHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        String msg =  macro_val;
        msg = msg + "\r";
        mSerialConnector.sendCommand(msg);
    }
}, 3000);


연속적인 시간 지연을 주는 코드를 테스트 해보고 적어둔다.

import android.content.Context;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.text.SimpleDateFormat;

public class MainActivity extends AppCompatActivity {
    Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // System클래스의 currentTimeMillis()메서드를 활용
        SimpleDateFormat timeformat = new SimpleDateFormat( "HH시 mm분 ss초");
        String time_display = timeformat.format (System.currentTimeMillis());
        System.out.println("start : "+time_display);

        final Handler handler = new Handler();
        for(int i=0; i < 20; i++){
            final int finalI = i+1;
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    SimpleDateFormat timeformat = new SimpleDateFormat( "HH시 mm분 ss초");
                    String time_display = timeformat.format (System.currentTimeMillis());
                    if(finalI <= 10){
                        System.out.println(time_display);
                    } else if(finalI >= 17){
                        System.out.println("반복 횟수 : " + finalI);
                    }

                }
            }, 1000*(i+1));
        }

    }
}


블로그 이미지

Link2Me

,
728x90

It depends on what is ENTER for your device.
In Windows it is CRLF (13 and then 10), Linux is LF (only 10.)
It's just a matter of what your device expects, because it can't see ENTER, just "byte 10, byte 13.


시리얼 통신에서 장비 콘솔 접속 제어 처리를 할 때 알아두어야 할 값이라 적어둔다.

    public final static char CR  = (char) 0x0D; // \r
    public final static char LF  = (char) 0x0A; // \n
    public final static char SPaceBar  = (char) 0x20;


윈도우상에서 콘솔로 접속하여 처리하던 걸 안드로이드에서 하려고 한다면 윈도우에서 사용하는 키를 알아야 동작한다는 점만 알면 다음 고려사항은 쉽게 풀린다.


Converting Control Codes to ASCII, Decimal and Hexadecimal
The ^ symbol stands for Ctrl

Ctrl    ASCII     Dec     Hex    Meaning
^@     NUL    000     00     Null character
^A     SOH    001     01     Start of Header
^B     STX    002     02     Start of Text
^C     ETX    003     03     End of Text
^D    EOT    004     04     End of Transmission
^E     ENQ     005     05     Enquiry
^F     ACK     006     06     Acknowledge
^G    BEL    007     07     Bell
^H     BS     008     08     Backspace
^I     HT     009     09    Horizontal tab
^J    LF     010     0A     Line feed
^K     VT     011     0B     Vertical tab
^L     FF    012     0C     Form feed
^M     CR     013     0D     Carriage return
^N     SO     014     0E     Shift out
^O     SI     015     0F     Shift in
^P     DLE     016     10     Data link escape
^Q     DCL     017     11     Xon (transmit on)
^R     DC2     018     12     Device control 2
^S     DC3     019     13    Xoff (transmit off)
^T     DC4     020     14     Device control 4
^U     NAK     021     15     Negative acknowledge
^V    SYN     022     16     Synchronous idle
^W    ETB     023     17     End of transmission
^X    CAN     024     18     Cancel
^Y    EM     025     19     End of medium
^Z    SUB     026     1A     Substitute
^[    ESC     027     1B     Escape
^\    FS     028     1C     File separator
^]    GS     029     1D     Group separator
^^    RS     030     1E    Record separator
^_    US     031     1F     Unit separator
        SP     032     20     Space

블로그 이미지

Link2Me

,
728x90

baudRate, databits, stopbit,parity 등을 선택하는 코드를 구현하고 간단한 사항을 적어둔다.

SharedPreferences 에 저장된 값을 읽어서 세팅하는 화면에 표시하고, 저장된 값이 없으면 default 세팅값을 보여준다.

select한 값을 SharedPreferences에 저장하는 코드까지 필요한 기능은 모두 구현했다.


pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
String BaudRate = pref.getString("BaudRate", ""); // 저장된 값 읽어오기

baudSpinner = (Spinner) findViewById(R.id.baudRateValue);
ArrayAdapter<CharSequence> baudAdapter = ArrayAdapter.createFromResource(context, R.array.baud_rate,
        R.layout.my_spinner_textview);
baudAdapter.setDropDownViewResource(R.layout.my_spinner_textview);
baudSpinner.setAdapter(baudAdapter);
if (BaudRate != null) { // 저장된 값이 있으면
    int spinnerPosition = baudAdapter.getPosition(BaudRate);
    baudSpinner.setSelection(spinnerPosition);
} else {
    baudSpinner.setSelection(0); // 없으면 default 실행
}
baudSpinner.setOnItemSelectedListener(new MyBaudRateSelectedListener());

public class MyBaudRateSelectedListener implements AdapterView.OnItemSelectedListener {
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        baudRate = parent.getItemAtPosition(position).toString(); // 선택한 값을 저장하기 위해
    }

    public void onNothingSelected(AdapterView<?> parent){

    }
}

case R.id.btn_save: // 저장
    pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
    SharedPreferences.Editor editor = pref.edit();
    editor.putString("BaudRate", baudRate);
    editor.commit();

    Intent intent = new Intent(Setting.this, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    startActivity(intent);
    break;



참고 : https://www.ftdichip.com/Android.htm 사이트에서 Java D2XX Update including FT4222H support 에 연결된 파일을 다운로드하여 java 파일과 resource 파일을 참조하면 구현하는데 도움된다.

블로그 이미지

Link2Me

,
728x90

어떤 칩셋은 정상동작하고 어떤 칩셋은 오동작이 발생한거 같다.

코드 사용 위치가 잘못되어 발생한 거 아닌가 싶다.


sendCommand() 는 Control transfer를 사용하여 Device로 Data를 보낸다.
UsbDeviceConnection.controlTransfer() 를 사용하여야 한다.

controlTransfer()의 proto type을 보면
public int controlTransfer (int requestType, int request, int value, int index, byte[] buffer, int length, int timeout)
requestType    request type for this transaction
request        request ID for this transaction
value        value field for this transaction
index        index field for this transaction
buffer        buffer for data portion of transaction, or null if no data needs to be sent or received
length        the length of the data to send or receive
timeout        in milliseconds


// for more info, search SET_LINE_CODING and SET_CONTROL_LINE_STATE in the document:
// "Universal Serial Bus Class Definitions for Communication Devices"

final int RQSID_SET_LINE_CODING = 0x20;
final int RQSID_SET_CONTROL_LINE_STATE = 0x22;

int usbResult;
usbResult = usbDeviceConnection.controlTransfer(0x21, // requestType
        RQSID_SET_CONTROL_LINE_STATE, // SET_CONTROL_LINE_STATE
        0, // value
        0, // index
        null, // buffer
        0, // length
        0); // timeout

// baud rate = 9600, 8 data bit, 1 stop bit
byte[] encodingSetting = new byte[] { (byte) 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
usbResult = usbDeviceConnection.controlTransfer(0x21, // requestType
        RQSID_SET_LINE_CODING, // SET_LINE_CODING
        0, // value
        0, // index
        encodingSetting, // buffer
        7, // length
        0); // timeout



블로그 이미지

Link2Me

,
728x90
USB Console 접속 코드를 구현해보는 중이다.

콘솔포트 - USB 케이블 - 스마트폰 연결을 하면 PC에 설치된 Android Studio 콘솔에서 Log 보는 것을 할 수가 없다.
에러가 발생하면 에러 메시지 분석이 쉽지 않다.

04-25 23:04:20.613: V/MainActivity(12928): VendorID : 1659 , ProductID : 8963
04-25 23:04:20.617: V/MainActivity(12928): controlTransfer(SET_CONTROL_LINE_STATE): 0
04-25 23:04:20.618: V/MainActivity(12928): controlTransfer(RQSID_SET_LINE_CODING): 7

04-25 23:20:54.285: V/MainActivity(18765): VendorID : 1027 , ProductID : 24577
04-25 23:20:54.289: V/MainActivity(18765): controlTransfer(SET_CONTROL_LINE_STATE): -1
04-25 23:20:54.291: V/MainActivity(18765): controlTransfer(RQSID_SET_LINE_CODING): -1


위와 같이 메시지가 스마트폰에 파일로 저장된다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Application이 원하는 장치에 대해서만 통보를 받을 수 있다. -->
    <!-- 0x0403 / 0x6001: FTDI FT232R UART -->
    <usb-device vendor-id="1027" product-id="24577" />
   
    <!-- 0x0403 / 0x6015: FTDI FT231X -->
    <usb-device vendor-id="1027" product-id="24597" />

    <!-- 0x2341 / Arduino -->
    <usb-device vendor-id="9025" />

    <!-- 0x16C0 / 0x0483: Teensyduino  -->
    <usb-device vendor-id="5824" product-id="1155" />

    <!-- 0x10C4 / 0xEA60: CP210x UART Bridge -->
    <usb-device vendor-id="4292" product-id="60000" />

    <!-- 0x1a86 / 0x7523: Qinheng CH340 -->
    <usb-device vendor-id="6790" product-id="29987" />

    <!-- 0x067B / 0x2303: Prolific PL2303 -->
    <usb-device vendor-id="1659" product-id="8963" />
    <usb-device vendor-id="1659" product-id="8964" />
    <usb-device vendor-id="1659" product-id="41216" />
    <!-- usb-device vendor-id="1659=0x067B" product-id="8963=0x2303" -->
    <!-- usb-device vendor-id="1659=0x067B" product-id="8964=0x2304" -->
    <!-- usb-device vendor-id="1659=0x067B" product-id="41216=0xA100" -->
</resources> 


칩셋이 FTDI 칩셋인 경우에 보여주는 메시지와 PL2303 칩셋일 때 보여주는 메시지가 다르다.
내가 테스트하는 코드가 PL2303은 정상적으로 잘 동작하는데 FTDI 칩셋은 오류를 뿌리고 바로 죽는다.

import android.os.Binder;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class LogWrapper {
    private static final String TAG = "LogWrapper";
    private static final int LOG_FILE_SIZE_LIMIT = 512 * 1024;
    private static final int LOG_FILE_MAX_COUNT = 2;
    private static final String LOG_FILE_NAME = "FileLog%g.txt";
    private static final SimpleDateFormat formatter =
            new SimpleDateFormat("MM-dd HH:mm:ss.SSS: ", Locale.getDefault());
    private static final Date date = new Date();
    private static Logger logger;
    private static FileHandler fileHandler;

    static {
        try {
            fileHandler = new FileHandler(Environment.getExternalStorageDirectory()
                    + File.separator +
                    LOG_FILE_NAME, LOG_FILE_SIZE_LIMIT, LOG_FILE_MAX_COUNT, true);
            fileHandler.setFormatter(new Formatter() {
                @Override
                public String format(LogRecord r) {
                    date.setTime(System.currentTimeMillis());

                    StringBuilder ret = new StringBuilder(80);
                    ret.append(formatter.format(date));
                    ret.append(r.getMessage());
                    return ret.toString();
                }
            });

            logger = Logger.getLogger(LogWrapper.class.getName());
            logger.addHandler(fileHandler);
            logger.setLevel(Level.ALL);
            logger.setUseParentHandlers(false);
            Log.d(TAG, "init success");
        } catch (IOException e) {
            Log.d(TAG, "init failure");
        }
    }

    public static void v(String tag, String msg) {
        if (logger != null) {
            logger.log(Level.INFO, String.format("V/%s(%d): %s\n",
                    tag, Binder.getCallingPid(), msg));
        }

        Log.v(tag, msg);
    }

}
 


위 코드의 출처는 https://abydos.tistory.com/23 이다.
테스트 결과 메시지가 출력되는 걸 확인할 수 있다.
Android Studio 에서 출력하는 완벽한 Log는 아니지만 에러가 발생하는 부분을 조금이라도 추적할 수는 있을 거 같다.
파일로 저장되므로 구글 드라이브에 공유로 폰에서 올리면 PC에서 EditPlus로 보기 편하게 볼 수 있더라.


아래 코드도 파일로 잘 저장된다.

    public static void writeLog(String str) {
        String str_Path_Full = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/download/logdata.txt";
        File file = new File(str_Path_Full);
        if (file.exists() == false) {
            try {
                file.createNewFile();
            } catch (IOException e) {
            }
        } else {
            try {
                BufferedWriter bfw = new BufferedWriter(new FileWriter(str_Path_Full,true));
                bfw.write(str);
                bfw.write("\n");
                bfw.flush();
                bfw.close();
            } catch (FileNotFoundException e) {

            } catch (IOException e) {

            }
        }
    }


외부저장소에 저장하기 위해서는 위험권한을 설정해야만 저장되므로 관련 코드는 구현해야 한다.

TEDpermission 으로 권한 설정하면 쉽게 할 수 있다.

블로그 이미지

Link2Me

,
728x90

안드로이드는 USB 통신을 위한 두가지 모드를 제공 : Host Mode, Accessory Mode


Host mode : 안드로이드 기기가 USB 호스트가 되어 장치에 전원을 공급하는 모드이다.
이 모드를 사용하려면 안드로이드 기기가 OTG를 지원해야 하고 OTG 케이블이 별도로 필요하다.
Android USB Host 모드는 안드로이드 3.1 이상에서 지원
Host 안드로이드 장비는 USB host로서의 역할을 수행하고 전력을 공급하며 연결된 USB 장비를 열거한다.


Interface와 Endpoint를 취득하여만 실제적으로 USB 통신을 진행할 수 있다.

- UsbManager, UsbDevice : device를 받고
- UsbInterface, UsbEndpoint : interface와 endpoint 를 찾고
- UsbDeviceConnection : 연결한다.


bulkTransfer() : 파라미터로 전달되는 Endpoint 방향에 따라서 Bulk Data의 송/수신 작업을 수행
controlTransfer() :  Endpoint Zero(0)로 Control Data의 송/수신 작업을 수행

Accessory mode : 안드로이드 기기가 주변기기가 되고 연결되는 USB 장치가 호스트가 되는 모드이다.


케이블을 구매할 때 별도의 전원이 공급되어야만 동작되는 케이블은 Accessory mode 로 동작한다고 보면 된다.

별도의 전원 공급없이 동작되는 케이블을 구입하는 것이 좋다.


모든 USB 장치는 Vendor ID 와 Product ID를 갖고 있다. 이 두개의 값으로 장치의 종류를 구분할 수 있다.


검색하면 "아두이노와 안드로이드" 연결에 대한 게시글들이 검색된다.

Android Serial 통신 소스코드는 https://github.com/godstale/Arduino-Serial-Controller 를 다운로드 한다.

usbSerialForAndroid 에 코드는 프로젝트에 별도 폴더로 옮기고 import 는 상황에 맞게 수정한다.

소스코드는 수정하지 않고, import 만 변경한다.


arduinocontroller 코드에 나온 4개의 파일을 약간 수정한다.

ArduinoControllerActivity.java 파일 수정

 - OTG Cable이 연결되면 자동으로 찾도록 BroadcastReceiver mUsbDeviceReceiver = new BroadcastReceiver() 에 대한 코드를 구글 검색하여 추가한다.

- Scrollview 시 키보드 입력에 따라 화면이 자동으로 변경되도록 코드를 추가한다.

  https://link2me.tistory.com/1613 게시글에 테스트한 코드를 참조하면 도움된다.

- https://android-er.blogspot.com/2014/03/android-send-command-to-arduino-in-usb.html 게시글을 참조하여

  두 게시글의 코드를 조합하여 만들면 원하는 결과를 얻을 수 있을 것이다.

- 나머지는 구글 플레이 스토어에서 USB Console 을 검색하여 앱을 사용해보고 원하는 기능을 추가한다.

- 핸들러 개념은 https://link2me.tistory.com/1233 참조한다. 좀 부족할 수 있다.

  https://zbomoon.tistory.com/10 참조

  링크된 예제에 구현된 handler 만으로도 충분히 원하는 결과를 얻을 수 있다.


SerialConnector.java 파일 수정

- UsbSerialDriver mDriver, UsbSerialPort mPort 는

   usbSerialForAndroid의 driver 파일과 연관되어 있으므로 모두 ArduinoController 폴더로 복사한다.

- public class SerialMonitorThread extends Thread 내 void run() 코드 중에서

  // Extract data from buffer 부분은 콘솔용으로 사용시에는 별 필요가 없다.

  SerialCommand.java 의 public void addChar(char c) 부분을 약간 수정한다.

public class SerialMonitorThread extends Thread {
    // Thread status
    private boolean mKillSign = false;
    private SerialCommand mCmd = new SerialCommand();

    private void initializeThread() {
        // This code will be executed only once.
    }

    private void finalizeThread() {
    }

    // stop this thread
    public void setKillSign(boolean isTrue) {
        mKillSign = isTrue;
    }

    /**
     *    Main loop
     **/
    @Override
    public void run() {
        byte readBuffer[] = new byte[4096]; // 아두이노 128

        while(!Thread.interrupted()) {
            if(sPort != null) {
                Arrays.fill(readBuffer, (byte)0x00);

                try {
                    // Read and Display to Terminal
                    int numBytesRead = sPort.read(readBuffer, 2000);
                    if(numBytesRead > 0) {
                        Log.d(tag, "run : read bytes = " + numBytesRead);

                        // Print message length
                        Message msg = mHandler.obtainMessage(Constants.MSG_READ_DATA_COUNT, numBytesRead, 0, new String(readBuffer));
                        mHandler.sendMessage(msg);

                    } // End of if(numBytesRead > 0)
                }
                catch (IOException e) {
                    Log.d(tag, "IOException - sDriver.read");
                    Message msg = mHandler.obtainMessage(Constants.MSG_SERIAL_ERROR, 0, 0, "Error # run: " + e.toString() + "\n");
                    mHandler.sendMessage(msg);
                    mKillSign = true;
                }
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }

            if(mKillSign)
                break;

        }    // End of while() loop
        // Finalize
        finalizeThread();
    }    // End of run()
}    // End of SerialMonitorT



나중에 기회되면 https://github.com/felHR85/UsbSerial 를 테스트 해보련다.


'안드로이드 > Android Serial 통신' 카테고리의 다른 글

usbDeviceConnection.controlTransfer  (0) 2019.04.27
Log 파일로 저장하는 코드  (0) 2019.04.25
ScrollView 데이터 자동 화면 표시  (0) 2019.02.19
Android USB Host  (0) 2019.02.15
UART 와 RS232C 개념 이해  (2) 2019.02.14
블로그 이미지

Link2Me

,
728x90

ScrollView 에 자동으로 가져온 데이터를 화면 출력하고 스크롤이 되도록 하는 샘플 코드다.

TextView 옵션을 못찾아 구글링으로 관련 코드를 찾으라 한참 삽질하고 동작 확인하고 세부 구현을 위한 기초 데이터로서는 되겠다 싶어 기초사항을 적는다.


 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_service_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="서비스 시작" />

        <Button
            android:id="@+id/btn_service_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="서비스 종료" />

        <Button
            android:id="@+id/btn_service_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="데이터 Get" />

        <Button
            android:id="@+id/btn_clear_data"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="화면 Clear" />

    </LinearLayout>

    <ScrollView
        android:id="@+id/scrollview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:descendantFocusability="beforeDescendants"
        android:gravity="center"
        android:background="#000000">

        <TextView
            android:id="@+id/consoleText"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_margin="5dp"
            android:textIsSelectable="false"
            android:gravity="left"
            android:textColor="#FFFFFF"/>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="3">

        <EditText
            android:id="@+id/EtInput"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="3"
            android:inputType="textMultiLine"
            android:lines="2"
            android:hint="명령어를 입력하세요" />

        <Button
            android:id="@+id/send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#00000000"
            android:text="send" />
    </LinearLayout>
</LinearLayout>


import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import static android.content.ContentValues.TAG;

public class getDataCallService extends Service {
    IBinder mBinder = new DataCallBinder();

    public getDataCallService() {
    }

    class DataCallBinder extends Binder {
        getDataCallService getService() { // 서비스 객체를 리턴
            return getDataCallService.this;
        }
    }

    public String getBroastData(){
        return "getDataCallService data\n Line Feed.";
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "서비스 onBind 에서 시작");
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "서비스 시작");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "서비스 종료");
        super.onDestroy();
    }
}

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.text.InputType;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    Context context;
    getDataCallService ms; // 서비스
    boolean isService = false; // 서비스 실행 확인
    TextView mDumpTextView;
    ScrollView mScrollView;
    private Runnable mScrollingRunnable;

    public EditText editText;
    public Button send;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 서비스와 연결되었을 때 호출되는 메서드
            getDataCallService.DataCallBinder mb = (getDataCallService.DataCallBinder) service;
            ms = mb.getService();
            isService = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isService = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        Button btnStart = (Button) findViewById(R.id.btn_service_start);
        Button btnEnd = (Button) findViewById(R.id.btn_service_end);
        Button btnCallData = (Button) findViewById(R.id.btn_service_data);
        Button btnClearData = (Button) findViewById(R.id.btn_clear_data);
        mScrollView = findViewById(R.id.scrollview);
        mDumpTextView = (TextView) findViewById(R.id.consoleText);
        editText = (EditText) findViewById(R.id.EtInput);
        send = (Button) findViewById(R.id.send);

        final SoftKeyboardDectectorView softKeyboardDecector = new SoftKeyboardDectectorView(this);
        addContentView(softKeyboardDecector, new FrameLayout.LayoutParams(-1, -1));

        softKeyboardDecector.setOnShownKeyboard(new SoftKeyboardDectectorView.OnShownKeyboardListener() {
            @Override
            public void onShowSoftKeyboard() {
                //키보드 등장할 때 채팅창 마지막 입력 내용을 바로 보이도록 처리
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        });

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 서비스 시작하기
                Intent intent = new Intent(getApplicationContext(), getDataCallService.class);
                bindService(intent,connection, Context.BIND_AUTO_CREATE);
            }
        });

        btnEnd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 서비스 종료
                unbindService(connection);
            }
        });

        btnClearData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDumpTextView.setText("");
                mScrollView.invalidate();
            }
        });

        // message send action
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!editText.getText().toString().isEmpty()) {
                    updateReceivedData(editText.getText().toString());
                    editText.setText(" ");
                }
            }
        });

        mScrollView.setVerticalScrollBarEnabled(true); // 수직방향 스크롤바 사용 가능하도록 설정
        mScrollView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false; // 이건 기능 테스트 목적으로 아직 구현이 안된 상태
            }
        });

        btnCallData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isService){
                    Toast.makeText(getApplicationContext(), "서비스가 실행중이 아닙니다.", Toast.LENGTH_SHORT).show();
                } else {
                    String data = ms.getBroastData(); // 서비스에서 가져온 데이터
                    updateReceivedData(data);
                }
            }
        });
    }

    private void updateReceivedData(String data) { // 콘솔 결과 데이터라 가정하고 테스트 함
        final String message = data + "\n";  // 실제 USB 콘솔 연결시에는 \n 은 삭제처리하는게 좋음
        mDumpTextView.append(message);

        mDumpTextView.setTextColor(Color.WHITE);
        mDumpTextView.setTextIsSelectable(false);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mDumpTextView.setElegantTextHeight(true);
        }
        mDumpTextView.setImeOptions(EditorInfo.IME_FLAG_NO_ENTER_ACTION);
        mDumpTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        mDumpTextView.setSingleLine(false);
        refreshView();
    }

    private void refreshView(){
        mScrollView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
            }
        },100);
        //mScrollView.setScrollY(View.FOCUS_DOWN);
        mScrollView.smoothScrollTo(0, mDumpTextView.getBottom());
    }
}

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

public class SoftKeyboardDectectorView extends View {
    // 출처 : http://givenjazz.tistory.com/54
    private boolean mShownKeyboard;
    private OnShownKeyboardListener mOnShownSoftKeyboard;
    private OnHiddenKeyboardListener onHiddenSoftKeyboard;

    public SoftKeyboardDectectorView(Context context) {
        this(context, null);
    }

    public SoftKeyboardDectectorView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diffHeight = (screenHeight - statusBarHeight) - h;
        if (diffHeight > 100 && !mShownKeyboard) { // 모든 키보드는 100px보다 크다고 가정
            mShownKeyboard = true;
            onShownSoftKeyboard();
        } else if (diffHeight < 100 && mShownKeyboard) {
            mShownKeyboard = false;
            onHiddenSoftKeyboard();
        }
        super.onSizeChanged(w, h, oldw, oldh);
    }

    public void onHiddenSoftKeyboard() {
        if (onHiddenSoftKeyboard != null)
            onHiddenSoftKeyboard.onHiddenSoftKeyboard();
    }

    public void onShownSoftKeyboard() {
        if (mOnShownSoftKeyboard != null)
            mOnShownSoftKeyboard.onShowSoftKeyboard();
    }

    public void setOnShownKeyboard(OnShownKeyboardListener listener) {
        mOnShownSoftKeyboard = listener;
    }

    public void setOnHiddenKeyboard(OnHiddenKeyboardListener listener) {
        onHiddenSoftKeyboard = listener;
    }

    public interface OnShownKeyboardListener {
        public void onShowSoftKeyboard();
    }

    public interface OnHiddenKeyboardListener {
        public void onHiddenSoftKeyboard();
    }

}


ScrollView 에서 TextView 값이 넘어오면 자동으로 화면이 스크롤 되도록 처리하는 것이 필요하다고 판단되어 관련 코드를 구글링하여 테스트하고 적었다.

아직 세부적으로 기능 설명은 적지 못했다.

블로그 이미지

Link2Me

,
728x90

안드로이드 Serial 통신 관련 게시글은 https://developer.android.com/guide/topics/connectivity/usb/host 에 나와 있다.


When your Android-powered device is in USB host mode, it acts as the USB host, powers the bus, and enumerates connected USB devices. USB host mode is supported in Android 3.1 and higher.

안드로이드 전력 장치가 USB host 모드일 때, USB host로서 역할을 수행하고, 버스에 전력을 공급하고, 연결된 USB 장치를 열거한다. USB host 모드는 안드로이드 3.1 이상에서 지원된다.


"android.hardware.usb" 패키지안에 USB host API들이 있다.

Class

 설 명

 UsbManager

 연결 USB 장치들을 열거하고 통신하도록 허용한다.

 UsbDevice

 연결된 USB 장치를 표시하고

 식별 정보, 인터페이스, endpoint에 접속하기 위한 메소드들을 포함한다.

 UsbInterface

 USB 장치의 인터페이스를 표시하고, 장치의 기능 셋이 정의되어 있다.

 장치는 하나 또는 이상의 통신 인터페이스를 갖고 있다.

 UsbEndpoint

 인터페이스 endpoint를 표시하며, 이 인터페이스에 대한 통신 채널이다.

 하나의 인터페이스는 하나 이상의 endpoint를 갖고 있으며,

 통상적으로 장치와 양방향통신을 위한 input, output endpint를 갖고 있다.

 UsbDeviceConnection

 장치에 연결을 표시하며, endpoint에 데이터를 전송한다.

 이 클래스는 데이터를 순방향, 역방향 동기/비동기 전송을 허용한다.

 UsbRequest

 UsbDeviceConnection을 통한 장치와의 통신을 위한 비동기 요청을 나타낸다.

 UsbConstants

 리눅스 커널의 linux/usb/ch9.h 에 정의되어져 있는 연관된 상수들이 정의되어져 있다.



How to Monitor USB Events on Android

https://github.com/yushulx/android-usb-monitor 를 받으면 Eclipse 기반으로 된 코드를 받게 된다.

이걸 Android Studio 에서 복사 및 붙여넣기 하면 된다.


Android Manifest.xml 파일 내용 수정

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.link2me.usbdevice">

    <uses-feature android:name="android.hardware.usb.host" />

    <!-- android.hardware.usb.host 를 쓴다고 알려 줘야 된다. -->

    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />

                <!-- USB Device 가 연결될때의 Action이라고 알려준다 -->
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />

                <!-- USB Device 정보가 res\xml\device_filter.xml 에 있다고  알려 준다 -->
        </activity>
    </application>

</manifest> 


device_filter.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Application이 원하는 장치에 대해서만 통보를 받을 수 있다. -->
    <!-- 0x0403 FTDI -->
    <usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
    <usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
    <usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
    <usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
    <usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT231X -->

    <!-- 0x2341 / Arduino -->
    <usb-device vendor-id="9025" />

    <!-- 0x16C0 / 0x0483: Teensyduino  -->
    <usb-device vendor-id="5824" product-id="1155" />

    <!-- 0x10C4 / 0xEAxx: Silabs CP210x -->
    <usb-device vendor-id="4292" product-id="60000" />
    <usb-device vendor-id="4292" product-id="60016" />
    <usb-device vendor-id="4292" product-id="60032" />
    <usb-device vendor-id="4292" product-id="60033" />
    <usb-device vendor-id="4292" product-id="60048" />

    <!-- 0x1a86 / 0x7523: Qinheng CH340 -->
    <usb-device vendor-id="6790" product-id="29987" />

    <!-- 0x067B / 0x2303: Prolific PL2303 -->
    <usb-device vendor-id="1659" product-id="8963" />
    <usb-device vendor-id="1659" product-id="8964" />
    <usb-device vendor-id="1659" product-id="41216" />

    <!-- 0x0d28 / 0x0204: ARM mbed -->
    <usb-device vendor-id="3368" product-id="516" />
</resources>


까지 하고 컴파일하면 USB 케이블 장치를 자동 인식한다.

여기까지 하면 USB 장치가 인식되는 걸 확인할 수 있다.

블로그 이미지

Link2Me

,
728x90

Serial 통신 구현 검토을 위해 관련 지식을 찾아서 정리차 적어둔다.


UART(Universal Asynchronous Receiver/Transmitter) : 비동기통신
- UART는 디지털 데이터 0과 1을 어떻게 통신할 것인지에 대한 통신 규격을 정해 놓은 것

- UART에서 나오는 신호는 보통 TTL 신호레벨을 갖기 때문에 노이즈에 약하고 통신거리에 제약이 있다.

- 하드웨어가 간단해서 거의 모든 장치에서 UART를 지원하기 때문에 (9개의 핀이 연결되거나, USB가 있는 장치라면) 편리하다.

- 1:1 통신만 지원한다. 동일한 속도를 맞추고 시작해야 한다. 그렇지 않으면 데이터가 깨질 것이다. Voltage level 이 동일해야 한다.

- 시리얼 통신의 기본 통신 규격은 1바이트 단위로 반복 전송되는 형식이며 바이트들 사이의 데이터 속성 구분 정보는 UART 기본 규격에서는 정의되어 있지 않다.

- UART로 연결된 두 디바이스 간 공유 클럭이 없으므로, 데이터를 올바르게 디코딩하기 위한 동일한 Baud Rate를 구성하고 일치시켜야 한다.

- 프로토콜에 9600 Baud Rate라고 나오는 경우 클럭이 9600 Hz로 작동한다는 뜻이다.

  (Serial 포트는 9600 Hz로 데이터 라인을 샘플링, 초 당 9600 Bit 전송)

  데이터 프레임의 Over Head (Start Bit, Stop 및 Parity Bit)가 포함되므로 유효 데이터 전송 속도는 약간 낮아지고, 구성한 프레임 Bit 수에 따라 달라진다.



USRT(Universal Synchronous Receiver/Transmitter) : 동기통신


대표적인 직렬통신 방식으로는 LAN , RS232 , X.25 등이 있다.


RS-232는 UART라는 통신 방식으로 데이터를 주고 받을 수 있게 정한 통신 장비(하드웨어)에 대한 규격이다.
- RS-232는 1969년 미국의 EIA(Electric Industries Association)에 의해 정해진 표준 인터페이스
- RS : Recommended Standard
- UART 통신을 할 수 있게 PC의 통신 하드웨어 규격을 표준으로 정해 놓은 것
- RS-232C는 한 번에 한 비트씩 전송되는 방식이고 통신 거리는 일반적으로 15 m(50ft)

- 최고 통신속도 20kb/s

- 지원 전송방식 : Full Duplex

- 최대 출력전압 : ± 25V

- 3V 이상의 신호 논리적으로 0을 나타내며 –3V이하의 전기적 신호가 들어오면 논리적으로 1을 나타내게 된다.



DCD(Data Carrier Detect) :

모뎀이 상대편 모뎀과 전화선 등을 통해서 접속이 완료되었을 때 상대편 모뎀이 캐리어 신호를 보내오며 이 신호를 검출하였음을 컴퓨터 또는 터미널에 알려주는 신호선이다.


RXD(Receive Data)
외부 장치에서 들어오는 직렬통신 데이터를 입력 받는 신호선이다.


TXD(Transmit Data)
비동기식 직렬통신 장치가 외부 장치로 정보를 보낼 때 직렬통신 데이터가 나오는 신호선이다.


DTR(Data Terminal Ready)
컴퓨터 또는 터미널이 모뎀에게 자신이 송수신 가능한 상태임을 알리는 신호선이며 일반적으로 컴퓨터 등이 전원 인가 후 통신 포트를 초기화한 후 이 신호를 출력시킨다.


DSR(Data Set Ready)
모뎀이 컴퓨터 또는 터미널에게 자신이 송수신 가능한 상태임을 알려주는 신호선이며 일반적으로 모뎀에 전원 인가 후 모뎀이 자신의 상태를 파악 한 후 이상이 없을 때 이 신호를 출력시킨다. 


RTS(Ready To Send)
컴퓨터와 같은 DTE장치가 모뎀 또는 프린터와 같은 DCE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.


CTS(Clear To Send)
모뎀 또는 프린터와 같은 DCE장치가 컴퓨터와 같은 DTE장치에게 데이터를 받을 준비가 됐음을 나타내는 신호선이다.


RI(Ring Indicator)
상대편 모뎀이 통신을 하기위해서 먼저 전화를 걸어오면 전화 벨이 울리게 된다. 이때 이 신호를 모뎀이 인식하여 컴퓨터 또는 터미널에 알려주는 신호선이며 일반적으로 컴퓨터가 이 신호를 받게 되면 전화벨 신호에 응답하는 프로그램을 인터럽터 등을 통해서 호출하게 된다.


참고하면 도움되는 게시글

http://www.hardcopyworld.com/ngine/aduino/index.php/archives/2080



PC에 꼽기만 하면 자동으로 Com Port를 할당해주는 케이블이다.


FTDI USB to UART COM Port Utility
This utility is for use with FTDI USB to UART devices. The utility provides a terminal emulation function for use on Android devices. The Android system must use Android OS version 3.2 or later and provide a USB host port.
Features
* The application will open automatically when you plug in supported FTDI device controller like FT232R, FT245R, FT232H, FT2232D, FT2232H, FT4232H and FT230X, FT231X...
* It supports FTDI USB TTL Serial, USB RS232 and USB Hi-Speed cables.
* Suitable for use on any Android platform with Android v3.2 and later versions.
* Provide general terminal UART utility; easily adaptable to a console function.
* Support CTS/RTS, DTR/DSR and XOFF/XON Flow controls.
* Support Baud from 300 to 921600.
* Save file and Send file functions support XModem, YModem and ZModem file transfer protocols.
* USB Plug and Play.
* USB 2.0 Full Speed compatible.


USB Host 기능을 이용한 시리얼 통신 방법
USB 장치가 OTG 케이블을 통해 연결되면 vendor-id와 product-id에 따라서 이 장치와 연결될 앱을 설정하는 부분이 있다.

SerialConnector 클래스에 initialize() 함수가 있다.
여기서 USB 장치가 연결되면 FTDI 모듈에 맞는 드라이버를 통해 통신할 준비를 한다.

블로그 이미지

Link2Me

,