由于串口开发涉及到 jni,所以开发环境需要支持 ndk 开发,如果未配置 ndk 配置的朋友,或者对 jni 不熟悉的朋友, 请查看上一篇文章, android 串口开发第一篇: 搭建 ndk 开发环境以及第一个 jni 调用程序 , 串口通信和 java 操作 io 类似,先打开串口,然后向串口发送或者读取数据,最后关闭串口,所以基本思路就是:
1. 对串口文件进行配置(波特率等),选择串口文件,打开串口,设备不同 , 可以读写的串口也不同.
2. 读写串口 , 读串口需要开一个子线程,然后死循环读取串口发送的数据
3. 关闭串口文件
其中打开,关闭串口是在 jni 方法执行,读写操作是 android 程序执行。
我的开发环境是 android studio 2.3.3 串口开发我创建一个支持 c++ 项目,然后在 cpp 目录下,创建一个 nateve-lib.cpp 的程序,将串口打开,串口关闭的程序复制进去即可,native-lib 程序中方法的命名规则需要根据你实际情况,稍作修改,cpp 中方法名格式为, Java_包名_调用 jni 方法的类名_方法名,如 Java_com_serialportdemo_SerialPort_open,此处一定要注意,android studio 生成的是 cpp 程序,不是 c 程序,这两个有一些区别的,比如:
- 我对c也不熟悉,以下语法有误请指出*.c的语法
- 变量定义
- jstring jstr2 = (*env) -> NewStringUTF(env, cstr);
- 方法定义
- JNIEXPORT jstring JNICALL Java_com_serialportdemo_MainActivity_encode()
- JNIEXPORT jstring JNICALL Java_com_serialportdemo_MainActivity_decode()
- *.cpp的语法
- jstring jstr2 =env->NewStringUTF(hello.c_str());
- extern "C
- " //如果这里不写
- extern "C"
- ,程序编译不会错,但android无法调用该方法,错误日志是找不到该方法
- JNIEXPORT jstring JNICALL Java_com_serialportdemo_MainActivity_encode()
- extern "C"
- JNIEXPORT jstring JNICALL Java_com_serialportdemo_MainActivity_decode()
串口打开,串口关闭代码如下:
- //获取波特率
- 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_com_serialportdemo_SerialPort_open(JNIEnv *env, jobject thiz, jstring path,jint baudrate) {
- int fd;
- speed_t speed;
- jobject mFileDescriptor;
- LOGD("init native Check arguments");
- /* Check arguments */
- {
- speed = getBaudrate(baudrate);
- if (speed == -1) {
- /* TODO: throw an exception */
- LOGE("Invalid baudrate");
- return NULL;
- }
- }
- LOGD("init native Opening device!");
- /* Opening device */
- {
- jboolean iscopy;
- const char *path_utf = env->GetStringUTFChars(path, &iscopy);
- LOGD("Opening serial port %s", path_utf);
- // fd = open(path_utf, O_RDWR | O_DIRECT | O_SYNC);
- fd = open(path_utf, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY);
- LOGD("open() fd = %d", fd);
- env->ReleaseStringUTFChars(path, path_utf);
- if (fd == -1) {
- /* Throw an exception */
- LOGE("Cannot open port %d",baudrate);
- /* TODO: throw an exception */
- return NULL;
- }
- }
- LOGD("init native Configure device!");
- /* Configure device */
- {
- struct termios cfg;
- if (tcgetattr(fd, &cfg)) {
- LOGE("Configure device tcgetattr() failed 1");
- close(fd);
- return NULL;
- }
- cfmakeraw(&cfg);
- cfsetispeed(&cfg, speed);
- cfsetospeed(&cfg, speed);
- if (tcsetattr(fd, TCSANOW, &cfg)) {
- LOGE("Configure device tcsetattr() failed 2");
- close(fd);
- /* TODO: throw an exception */
- return NULL;
- }
- }
- /* Create a corresponding file descriptor */
- {
- jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
- jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor,"<init>", "()V");
- jfieldID descriptorID = env->GetFieldID(cFileDescriptor,"descriptor", "I");
- mFileDescriptor = env->NewObject(cFileDescriptor,iFileDescriptor);
- env->SetIntField(mFileDescriptor, descriptorID, (jint) fd);
- }
- return mFileDescriptor;
- }
- //关闭串口程序
- extern "C"
- JNIEXPORT jint JNICALL
- Java_com_serialportdemo_SerialPort_close(JNIEnv * env, jobject thiz)
- {
- jclass SerialPortClass = env->GetObjectClass(thiz);
- jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor");
- jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
- jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I");
- jobject mFd = env->GetObjectField(thiz, mFdID);
- jint descriptor = env->GetIntField(mFd, descriptorID);
- LOGD("close(fd = %d)", descriptor);
- close(descriptor);
- return 1;
- }
android 方法就简单多了,首先来看串口操作类,在这个类中打开串口,测试没有做关闭串口的操作, jni 的 open 方法,返回一个 java.io.FileDescriptor 对像,串口操作类通过该对像, 获取文件的读写流操作对像.
- //加载so文件
- static {
- System.loadLibrary("native-lib");
- }
- /**
- * @param path 串口文件路径
- * @param baudrate 波特率,不同设备波特率有区别
- * */
- public SerialPort(String path, int baudrate) throws SecurityException,
- IOException {
- File device = new File(path);
- Logger.d(serialPortMsg());
- if (!device.canRead() || !device.canWrite()) {
- try {
- Process su = Runtime.getRuntime().exec("/system/bin/su");
- String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";
- su.getOutputStream().write(cmd.getBytes());
- if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
- throw new SecurityException();
- }
- } catch(Exception e) {
- e.getMessage();
- }
- }
- mFd = open(device.getAbsolutePath(), baudrate);
- Logger.d(TAG + "open commplete");
- if (mFd == null) {
- Logger.e(TAG, "native open returns null");
- throw new IOException();
- }
- mFileInputStream = new FileInputStream(mFd);
- mFileOutputStream = new FileOutputStream(mFd);
- } //定义本地方法public native FileDescriptor open(String path, int baudrate); public native void close();
接下来需要定义一个读取串口信息的线程,用于获取串口发送给 android 的信息
- class ReadSerialPortMsgThread implements Runnable{
- @Override
- public void run() {
- int size;
- byte buff[] = new byte[1024];
- final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
- while (true){
- try {
- if(mInputStream==null){
- return;
- }
- size = mInputStream.read(buff);
- if(size<=0){
- continue;
- }
- final String message = new String(buff,0,size);
- Logger.d(TAG+"接收到串口回调 "+message);
- seriapPortMsg.append(message);
- if(buff[size - 1] == '\n'){
- log.post(new Runnable() {
- @Override
- public void run() {
- log.setText(sdf.format(new Date())+"接收到串口发送的指令 "+message);
- }
- });
- }
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
以上代码完成了对串口的读操作,串口写操作比较简单,就是得到串口的 OutputStream, 然后调用 writer 方法即可,代码如下:
- @Override public void onClick(View view) {
- switch (view.getId()) {
- case R.id.sendMsg:
- String msg = serMsg.getText().toString() + "\r\n";
- if (msg != null && !msg.equals("")) {
- byte[] buff = msg.getBytes();
- try {
- mOutputStream.write(buff, 0, buff.length);
- Logger.d(TAG + "msg 输出完成");
- } catch(IOException e) {
- e.printStackTrace();
- Logger.e(TAG + e.getMessage());
- }
- }
- }
- }
到此为止,读写操作的代码全部完成,我的测试串口设备一直在向 android 发送信息,如下图
String SERIALPORT_NO3 = "/dev/ttyS3",int BAUDRATE=115200; 这是我设备定义的串口文件路径和波特率,这个信息位置需要根据实际情况作修改。
完整 demo 代码:https://github.com/jlq023/serialport来源: https://www.cnblogs.com/cq-jiang/p/8145747.html