最近公司內部有個需求,需要開發一個 Android app 裝在我們的機器上,讓它可以透過 UART port 跟機器溝通。過程基本上不難,但還挺繁雜的,乾脆做個紀錄以便日後查詢。

我的開發環境是 Windows 10 搭配 Android Studio 4.1.2

照著做

Settings 裡頭,確認 NDKCMake 都有安裝了。

建立一個新專案,然後選擇 New -> Folder -> JNI Folder,我們要透過 JNI 的方式調用 C 程式碼跟 UART 溝通。

下載這個壓縮檔,解開之後有五個檔案,把它們放到剛剛建立的 JNI 目錄裡頭。

java 目錄按右鍵,選擇 New -> Package,建立一個名叫 android_serialport_api 的 package。注意:一定要叫這個名字。

在剛剛建立的 package 裡頭建立一個 Java Class,名字要叫做 SerialPort

這時候應該產生一個 SerialPort.java 了,用這個檔案替換它的內容。

app 目錄按右鍵,選擇 Link C++ Project with Gradle

此時會跳出一個對話框,Build System 選擇 ndk-buildProject Path 選擇剛剛放到 JNI 目錄的 Android.mk

選好之後就可以 Build -> Make Project,一切順利的話會產出 libserial_port.so

做為測試用的 app,我們的 MainActivity.java 可以這樣寫,記得把 UART port 的路徑以及 baud rate 改成你的環境:

import android_serialport_api.SerialPort;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    protected SerialPort mSerialPort;
    protected InputStream mInputStream;
    protected OutputStream mOutputStream;
    private ReadThread mReadThread;

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();

            Log.e(TAG, "Read thread is open.");
            while (!isInterrupted()) {
                if (mInputStream == null)
                    return;

                try {
                    byte[] buffer = new byte[64];
                    int size = mInputStream.read(buffer);
                    if (size > 0) {
                        onDataReceived(buffer, size);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

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

        try {
            Log.e(TAG, "Try to open serial port");
            mSerialPort = new SerialPort(new File("/dev/ttyXXX"), 115200, 0);
            mInputStream = mSerialPort.getInputStream();
            mOutputStream = mSerialPort.getOutputStream();
            Log.e(TAG, "Serial port opened!");

            mReadThread = new ReadThread();
            mReadThread.start();
        } catch (SecurityException e) {
            Log.e(TAG, "Security Exception");
            e.printStackTrace();
        } catch (IOException e) {
            Log.e(TAG, "Fail to open serial port");
            e.printStackTrace();
        }

        try {
            mOutputStream.write("help".getBytes());
            mOutputStream.write('\n');
        } catch (IOException e) {
            Log.e(TAG, "Fail to send out message");
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "in onDestroy()");
        if (mReadThread != null) {
            mReadThread.interrupt();
        }

        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;

            try {
                mOutputStream.close();
                mOutputStream = null;
                mInputStream.close();
                mInputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        super.onDestroy();
    }

    protected void onDataReceived(final byte[] buffer, final int size) {
        runOnUiThread(() -> {
            String receivedMessage = new String(buffer, 0, size);
            Log.d(TAG, "Received message: " + receivedMessage);
        });
    }
}

我們可以試試看 Build -> Clean Project 然後 Build -> Rebuild Project,確認一切都正常,沒事的話就可以跑在機器上了。

問題排解

Q:Compile 失敗了
A:確認剛剛建立的 packange 名稱是 android_serialport_api,以及建立的 class 名稱是 SerialPort.java

Q:照做了還是無法通訊?
A:確認 Android 跟 port 是可以通的,有可能根本不能存取那個 port。

Q:確認過了還是無法通訊?
A:確認路徑跟 baud rate 是對的。

Q:確認過路徑跟 baud rate 了,還是無法通訊?
A:可能是權限問題,執行以下命令再試試看。

adb shell
su
chmod 777 /dev/ttyXXX
setenforce 0

Q:如果還是不行呢?
A:換一台硬體試試看。

參考資料