Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 關於Android串口通訊總結

關於Android串口通訊總結

編輯:關於Android編程

前言

這幾天完了串口,暫時還沒搞懂這是啥玩意,因為目前底層到底如何通訊的我還不知道,不過這裡先總結一下這兩天的收獲。

正文

現在我們開始我們最主要的問題,因為串口作為底層實現,linux把設備作為文件,並且串口文件在dev目錄下的,並且現在都是通過c代碼來打開的(貌似Java無法設置波特率啥的,這個東西c代碼我暫時也搞不懂。並且我們cat這個文件的時候是得不到文件。以後我有機會研究研究,說不定可以實現呢。)。
這裡我們關注兩個問題。一個是路徑,因為我們Android貌似好幾個串口。所以你一定要知道你鏈接的串口的路徑。第二波特率,這個是傳輸頻率。非常重要,不然會出現亂碼。一般如果我們可以收到消息,不能正常工作,大部分都是波特率不對。

對於不清楚如何使用jni的。我這裡我推薦兩個東西,Android Studio很強大,已經支持直接編譯c/c++代碼了,所以呢(Android studio 2.2+版本)。這裡我就不再介紹之前用Javah的方法來編譯jni的代碼。這裡我還是不再詳細介紹,後續我會給大家一個詳細教程。

首先我們對於我們關注的串口我們會有兩個操作,打開和關閉。

#include 
#include 
#include 
#include 
#include 
#include 
//這裡有沒有簡單的方法我也不知道啊。為啥這麼怪的實現方法呢。大家可以給我指點。我只能這樣搞了!!!
#define TAG "serial Port" // 這個是自定義的LOG的標識
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定義LOGD類型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定義LOGI類型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定義LOGW類型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定義LOGE類型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定義LOGF類型
static speed_t getBaudRate(jint baudRate) {
    switch (baudRate) {
        case 0: return B0;
        case 50: return B50;
        case 75: return B75;
        case 110: return B110;
        case 134: return B134;
        case 150: return B150;
        case 200: return B200;
        case 300: return B300;
        case 600: return B600;
        case 1200: return B1200;
        case 1800: return B1800;
        case 2400: return B2400;
        case 4800: return B4800;
        case 9600: return B9600;
        case 19200: return B19200;
        case 38400: return B38400;
        case 57600: return B57600;
        case 115200: return B115200;
        case 230400: return B230400;
        case 460800: return B460800;
        case 500000: return B500000;
        case 576000: return B576000;
        case 921600: return B921600;
        case 1000000: return B1000000;
        case 1152000: return B1152000;
        case 1500000: return B1500000;
        case 2000000: return B2000000;
        case 2500000: return B2500000;
        case 3000000: return B3000000;
        case 3500000: return B3500000;
        case 4000000: return B4000000;
        default:
            return -1;
    }
}

extern "C"
JNIEXPORT jobject JNICALL Java_zzxcomm_keylock_Util_SerialPort_open
        (JNIEnv *env, jobject obj, jstring devicePath, jint buaRate, jint flags) {
    int fd;
    speed_t speed;
    jobject mFd;
    speed   = getBaudRate(buaRate);
    if (buaRate == 115200){
        LOGE("Invalid buaR");
    }
    if (speed == -1) {
        LOGE("Invalid buaRate!!");
        return NULL;
    }
    LOGI("Right buaRate = %d.", buaRate);
    /** open device */
    jboolean isCopy;
    const char *utfPath = env->GetStringUTFChars(devicePath, &isCopy);
    fd  = open(utfPath, O_RDWR | flags);
    env->ReleaseStringUTFChars(devicePath, utfPath);
    if (fd == -1) {
        LOGE("Cannot open port");
    }
    LOGI("Open port Success!");
    /** Configure Device*/
    struct termios cfg;
    /** 獲取與該終端描述符有關的參數,結果保存在termios結構體中.成功返回0
    c_iflag:輸入模式標志,控制終端輸入方式.
    c_oflag:輸出模式標志.
    c_cflag:控制模式標志,指定終端硬件控制信息.
    c_lflag:本地模式標志,控制終端編輯功能.
    c_cc[NCCS]:控制字符,用於保存終端驅動程序中的特殊字符,如輸入結束符.
    **/
    if (tcgetattr(fd, &cfg)) {
        LOGE("tcgetattr() failed");
        close(fd);
        return NULL;
    }
    LOGI("tcgetattr() Success");
    /** 設置終端屬性為原始屬性 **/
    cfmakeraw(&cfg);
    /** 設置輸入波特率 */
    cfsetispeed(&cfg, speed);
    /** 設置輸出波特率 */
    cfsetospeed(&cfg, speed);
    /** 設置屬性
    第二個參數表示什麼時候生效.
    TCSANOW:表明該設置立即生效
    TCSADRAIN:在所有寫入fd的輸出都輸出後生效.此參數該在參數影響輸出時使用
    TCSAFLUSH:清空輸入輸出緩沖區才改變屬性.所有寫入 fd 引用的對象的輸出都被傳輸後生效,所有已接受但未讀入的輸入都在改變發生前丟棄.
    **/
    if (tcsetattr(fd, TCSANOW, &cfg)) {
        LOGE("tcsetattr() failed");
        close(fd);
        return NULL;
    }
    LOGI("tcsetattr() Success");
    /** Create a corresponding file descriptor */
    jclass cFileDescriptor  = env->FindClass("java/io/FileDescriptor");
    jmethodID iFileDescriptor   = env->GetMethodID(cFileDescriptor, "", "()V");
    jfieldID descriptorID   = env->GetFieldID(cFileDescriptor, "descriptor", "I");
    mFd = env->NewObject(cFileDescriptor, iFileDescriptor);
    env->SetIntField(mFd, descriptorID, (jint) fd);
    LOGI("return mFd = %d.", fd);
    return mFd;
}

/*
 * Class:     com_zzx_port_SerialPort
 * Method:    close
 * Signature: ()V
 */
extern "C"
JNIEXPORT void JNICALL Java_zzxcomm_keylock_Util_SerialPort_close
        (JNIEnv *env, jobject obj) {
    jclass SerialPortClass = env->GetObjectClass(obj);
    jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor");
    jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");

    jfieldID mFDID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jobject mFd = env->GetObjectField(obj, mFDID);

    jint descriptor = env->GetIntField(mFd, descriptorID);
    close(descriptor);
}

這裡的東西我也不太懂,總是就是獲取了一個文件的操作指針,也是句柄。總之就是你獲取了文件的操作方法。。然後我們看下Java代碼

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Created by Administrator on 2016/6/17.
 * 串口通信
 */
public class SerialPort {

    private FileInputStream mInput;
    private FileOutputStream mOutput;
    private FileDescriptor mFd;

    private native FileDescriptor open(String path, int baudRate, int flags);

    public native void close();

    static {
        System.loadLibrary("native-lib");
    }

    /**
     * 構造函數
     * @param portPath 串口路徑.
     * @param baudRate 串口波特率.
     * @param flags    串口類型.一般為0.
     */
    public SerialPort(String portPath, int baudRate, int flags) throws SecurityException, IOException {
        File file = new File(portPath);
        if (!file.canRead() || !file.canWrite()) {
        //這裡很重要,在Android 5.0 之後,這裡無法獲取root 權限,所以無法讀取到我們串口的消息。所以呢,只能找底層人幫忙了。
            try {
                Process su;
                su = Runtime.getRuntime().exec("/system/xbin/su");
                String cmd = "chmod 666 " + portPath + "\n";
                su.getOutputStream().write(cmd.getBytes());
                if ((su.waitFor() != 0) || !file.canWrite() || !file.canRead()) {
                    throw new SecurityException();
                }
                su.destroy();
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }
        try {
            mFd = open(portPath, baudRate, flags);
            if (mFd == null) {
                throw new IOException();
            }
        } catch (Exception e) {
            e.printStackTrace();
            LogUtil.loge("serial port","is failed");
            return;
        }
        mInput = new FileInputStream(mFd);
        mOutput = new FileOutputStream(mFd);
        LogUtil.loge("serial port","is open");
    }

    /**
     * 獲取輸入流
     */
    public InputStream getInputStream() {
        return mInput;
    }

    /**
     * 獲取輸出流
     */
    public OutputStream getOutputStream() {
        return mOutput;
    }

    public void doClose(){
        close();
    }
}

這裡打開文件。然後獲取到io流,以便於在應用中讀取,其實這裡我們已經獲取到我們該得到的東西了,不過我們還是注意一下,因為我們需要不停的讀取這個串口,近似於監聽效果,並且我們這裡使用一個單例模式,以便於獲取與不至於是程序混亂。代碼如下

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 串口操作類
 *
 * @author Jerome
 *
 */
public class SerialPortUtil {
    private SerialPort mSerialPort;
    private OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    private String path = "/dev/ttyMT0";
    private int baudrate = 115200;
    private static SerialPortUtil portUtil;
    private OnDataReceiveListener onDataReceiveListener = null;
    private boolean isStop = false;

    public interface OnDataReceiveListener {
        void onSerialDataReceive(byte[] buffer, int size);
    }

    public void setOnDataReceiveListener(
            OnDataReceiveListener dataReceiveListener) {
        onDataReceiveListener = dataReceiveListener;
    }

    public static SerialPortUtil getInstance() {
        if (null == portUtil) {
            portUtil = new SerialPortUtil();
            portUtil.onCreate();
        }
        return portUtil;
    }

    /**
     * 初始化串口信息
     */
    public void onCreate() {
        try {
            mSerialPort = new SerialPort(path, baudrate,0);
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();

            mReadThread = new ReadThread();
            isStop = false;
            mReadThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean sendData(byte[] data){
        boolean result = true;
        try {
            if (mOutputStream != null) {
                mOutputStream.write(data);
                LogUtil.logd("發出",data);
            } else {
                result = false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    public void sendBuffer(byte[] mBuffer) {
        if (!sendData(mBuffer)){
            closeSerialPort();
            onCreate();
            sendBuffer(mBuffer);
        }
    }

    private class ReadThread extends Thread {

        @Override
        public void run() {
            super.run();
            while (!isStop && !isInterrupted()) {
                int size;
                try {
                    if (mInputStream == null)
                        return;
                    byte[] buffer = new byte[512];
                    size = mInputStream.read(buffer);
                    if (size > 0) {
                        if (null != onDataReceiveListener) {
                            onDataReceiveListener.onSerialDataReceive(buffer, size);
                        }
                    }
                    Thread.sleep(10);
                } catch (Exception e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

    /**
     * 關閉串口
     */
    public void closeSerialPort() {
        isStop = true;
        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        if (mSerialPort != null) {
            try {
                mSerialPort.doClose();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}

這裡我們就已經獲取了串口的讀取與發送的代碼控制了。基本算是整個個流程都完成了。不在詳細介紹,對於接收數據後如何處理。這裡本人也沒找到好的辦法,如果只在一個界面前面代碼保留了一個監聽接口,基本滿足我們需求,但是如果要分發數據的話,這裡就不太容易處理了,
這裡提供幾個思路

eventbus、或者廣播。這東西確實對於解耦很有用,缺點:黑洞效應比較明顯,對於簡單應用,有點復雜。

直接設置一個OnDataReceiveListener,用來分發消息。這裡邏輯更加簡單,可是遇到更加復雜的問題,很難解決

這裡具體如何選擇我就不再詳細介紹。對於選擇困難綜合症患者。建議eventbus。

注意事項

我們通過數組得到的一般是inputstreame,然後很容易轉換成byte數組,這裡我們很容易知道每個byte包含8個字節,可以存儲256個字符,但是這些字符有些無法表示,所以呢在很多通訊協議中都是講一個byte轉換成兩個十六進制的數字表示,這裡我知道的大概有四五種解析方法,我這裡不一一解釋,這篇博客暫時只給出一個方法。以後我有空再給大家補充

final static String HEX = "0123456789ABCDEF";
private String getHexString(byte[] buffer) {
    StringBuilder sb = new StringBuilder(buffer.length * 2);
    for (int i=0;i> 4) & 0x0f));
            //取出字節的低4位,然後與 0x0f與運算,得到0~15的數據,通過HEX.charAt(0~15),即為十六進制數.
        sb.append(HEX.charAt(buffer[i] & 0x0f));
    }
    LogUntils.logv(this,sb.toString());
    return sb.toString();
}

具體我就不解釋了,這裡邏輯比較簡單。小面我們稍微說點小技巧。其實很簡單,但是我總是忘記,


因為byte是8位,理論是上無符號,但是假如最高位為1的時候,再向int型轉換的時候,會變成有符號的數據,這裡我們記住,byte是個八byt的二進制數值,強制轉換成int的時候只取這個八位的數值,所以會出現負數,當我們使用int型時,在八byt的范圍內永遠不會有負值。
而int型向byte轉換僅僅取第八位的數值,因為byte不關心正負號,他僅僅是一種編碼符號。所以加入我們需要判斷獲取的一個byte和int型。一般是可以

byt buffer = a;
int code =  97;
if((byte)code  == buffer){
}

或者:

byte buffer = a;
int code =  97;
if(code  == (byt)buffer&0xff){
}

這裡byte如果簡單的算法還是要知道Java的數據存儲方式比較好。具體自己理解。


Android 5.0之後包括5.0,權限貌似有問題。具體如何讀取串口。我暫時沒找到好的方法


還有幾個問題。關於消息的處理。當讀取的消息不是一條,也就是在那個時間段內,讀取的東西不是一條消息指令。我們需要截取指令長度,然後處理。這裡可以結合自己code來處理

如果中間發錯指令或者,或者需要重新發送。這裡需要建立一個消息隊列,這裡的問題比較復雜,不在詳細介紹,這種情況一般很少發生,基本可以不用考慮。

後記

總之這個算是寫完了,遇到一些問題,希望大家指正.具體代碼
一個版本的源碼:這裡寫代碼片

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved