Android串口通信

Android串口通信

前言

Android需要与下位机通信的情况下,如果对传输速率要求不高的话,串口通信是很可靠且最常用的一种方案

分析

官方提供了一个开源项目(android-serialport-api)提供了串口使用的方法,但是并不能直接使用,需要集成到自己的项目中,下面来分析下怎么使用串口

串口使用和一般的硬件设备一样,无外乎四个步骤:

1.打开串口(及配置串口)
2.读串口
3.写串口
4.关闭串口

因此如果想要使用串口的话就需要使用jni来进行HAL层到应用层的中间层开发

使用

先来看下jni接口

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;
    }
}

JNIEXPORT jobject JNICALL Java_com_gavinandre_serialportlibrary_SerialPort_open
  (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{
    int fd;
    speed_t speed;
    jobject mFileDescriptor;

    /* Check arguments */
    {
        speed = getBaudrate(baudrate);
        if (speed == -1) {
            /* TODO: throw an exception */
            LOGE("Invalid baudrate");
            return NULL;
        }
    }

    /* Opening device */
    {
        jboolean iscopy;
        const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
        LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
        fd = open(path_utf, O_RDWR | flags);
        LOGD("open() fd = %d", fd);
        (*env)->ReleaseStringUTFChars(env, path, path_utf);
        if (fd == -1)
        {
            /* Throw an exception */
            LOGE("Cannot open port");
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* Configure device */
    {
        struct termios cfg;
        LOGD("Configuring serial port");
        if (tcgetattr(fd, &cfg))
        {
            LOGE("tcgetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }

        cfmakeraw(&cfg);
        cfsetispeed(&cfg, speed);
        cfsetospeed(&cfg, speed);

        if (tcsetattr(fd, TCSANOW, &cfg))
        {
            LOGE("tcsetattr() failed");
            close(fd);
            /* TODO: throw an exception */
            return NULL;
        }
    }

    /* Create a corresponding file descriptor */
    {
        jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
        jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
        jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
        mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
        (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
    }

    return mFileDescriptor;
}

JNIEXPORT void JNICALL Java_com_gavinandre_serialportlibrary_SerialPort_close
  (JNIEnv *env, jobject thiz)
{
    jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
    jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

    jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
    jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");

    jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
    jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);

    LOGD("close(fd = %d)", descriptor);
    close(descriptor);
}

根据函数名可以发现,三个函数分别是设置波特率,打开串口,关闭串口

有一个要注意的地方,就是jni接口函数名需要把包名类名都写完整,因为jni是通过反射去调用c层函数的,因此函数名编写要根据固定的格式:包名+类名+函数名

下面来看下SerialPort这个类

public class SerialPort {

    private static final String TAG = "SerialPort";

    /**
     * Do not remove or rename the field mFd: it is used by native method close();
     */
    private FileDescriptor mFd;
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

        /* Check access permission */
        if (!device.canRead() || !device.canWrite()) {
            try {
                /* Missing read/write permission, trying to chmod the file */
                Process su;
                su = Runtime.getRuntime().exec("su");
                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
                        + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                su.getOutputStream().flush();
                Log.i(TAG, "SerialPort: " + cmd);
                //DataOutputStream os = new DataOutputStream(su.getOutputStream());
                //os.writeBytes(cmd);
                //os.flush();
                if ((su.waitFor() != 0) || !device.canRead()
                        || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }

        mFd = open(device.getAbsolutePath(), baudrate, flags);
        if (mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        }
        mFileInputStream = new FileInputStream(mFd);
        mFileOutputStream = new FileOutputStream(mFd);
    }

    // Getters and setters
    public InputStream getInputStream() {
        return mFileInputStream;
    }

    public OutputStream getOutputStream() {
        return mFileOutputStream;
    }

    // JNI
    private native static FileDescriptor open(String path, int baudrate, int flags);

    public native void close();

    static {
        System.loadLibrary("SerialPort");
    }
}

这个类的功能就是调用jni接口,来打开或关闭串口设备,当中有一段代码是获取root权限,然后给串口设备(/dev/ttyXXXX)获取获取读写权限,Android4.4以后这个功能就失效了,如果想获取权限的话建议使用adb shell来获取权限

adb shell获取权限步骤:

adb shell
su
chmod 777 /dev/ttyXXXX(your serial port deivce)
exit

还有一个类是SerialPortFinder,这个类是用来找到系统中可以用的串口的,如果你知道的android设备有什么串口,就不必使用这个类来查找串口了

串口封装类

public class SerialPortUtil {

    private static final String TAG = "SerialPortUtil";
    public SerialPortFinder mSerialPortFinder = new SerialPortFinder();
    private SerialPort mSerialPort = null;

    public SerialPort getSerialPort() throws SecurityException, IOException, InvalidParameterException {
        if (mSerialPort == null) {
            mSerialPort = new SerialPort(new File("/dev/ttyAMA2"), 115200, 0);
        }
        return mSerialPort;
    }

    public void closeSerialPort() {
        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;
        }
    }
}

这个类里我们封装了下打开串口和关闭串口的方法,方便直接调用

抽象Activity

public abstract class SerialPortActivity extends AppCompatActivity {

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

    HandlerThread sendingHandlerThread = new HandlerThread("sendingHandlerThread");

    {
        sendingHandlerThread.start();
    }

    protected Handler sendingHandler = new Handler(sendingHandlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (mOutputStream != null) {
                try {
                    mOutputStream.write((byte[]) msg.obj);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                int size;
                try {
                    byte[] b = new byte[64];
                    if (mInputStream == null) {
                        return;
                    }
                    size = mInputStream.read(b);
                    byte[] buffer = new byte[size];
                    System.arraycopy(b, 0, buffer, 0, buffer.length);
                    if (size > 0) {
                        onDataReceived(buffer, size);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }

    private void displayError(String resourceId) {
        AlertDialog.Builder b = new AlertDialog.Builder(this);
        b.setTitle("Error");
        b.setMessage(resourceId);
        b.setPositiveButton("OK", new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                SerialPortActivity.this.finish();
            }
        });
        b.show();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSerialPortUtil = new SerialPortUtil();
        try {
            mSerialPort = mSerialPortUtil.getSerialPort();
            mOutputStream = mSerialPort.getOutputStream();
            mInputStream = mSerialPort.getInputStream();

            //Create a receiving thread
            mReadThread = new ReadThread();
            mReadThread.start();
        } catch (SecurityException e) {
            // You do not have read/write permission to the serial port.
            displayError("You do not have read/write permission to the serial port.");
        } catch (IOException e) {
            // The serial port can not be opened for an unknown reason.
            displayError("The serial port can not be opened for an unknown reason.");
        } catch (InvalidParameterException e) {
            // Please configure your serial port first.
            displayError("Please configure your serial port first.");
        }
    }

    protected abstract void onDataReceived(final byte[] buffer, final int size);

    @Override
    protected void onDestroy() {
        if (mReadThread != null) {
            mReadThread.interrupt();
        }
        mSerialPortUtil.closeSerialPort();
        mSerialPort = null;
        if (null != sendingHandlerThread) {
            sendingHandlerThread.quit();
        }
        super.onDestroy();
    }
}

这里我们建了个抽象Activity,开了两个线程,ReadThread线程用来读串口的数据,HandlerThread用来发送串口数据

为什么要用HandlerThread来作为发送线程呢,因为Handler内部实现了一个MessageQuene,我们每次通过handler发送一个消息后就会加入这个队列内,就算在不同线程内使用也不会出现粘包之类的情况

在MainActivity内使用

最后只要在MainActivity中继承这个抽象Activity,就能非常方便的使用了

    public void send(String text) {
        Message message = Message.obtain();
        message.obj = text.getBytes();
        sendingHandler.sendMessage(message);
    }

    @Override
    protected void onDataReceived(final byte[] buffer, final int size) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "收到消息:" + new String(buffer) + "  size = " + size, Toast.LENGTH_SHORT).show();
            }
        });
    }

使用handler就能发送串口消息,重写onDataReceived方法就能接收串口消息

demo地址

https://github.com/GavinAndre/AndroidSerialPortSample

参考:

https://code.google.com/archive/p/android-serialport-api/

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值