Android 串口的踩坑之路

Android 串口配置与实战
本文记录了在Android上对接串口称时遇到的困难,包括配置NDK、解决安全异常和报名错误。关键步骤包括确认硬件支持、确定正确地址和波特率,以及处理包名问题。运行条件为硬件支持串口输出、知晓准确硬件参数和正确引用jniLibs。作者提供了详细的操作指南和解决方法。

最近公司需要对接串口称,我在网上查了好多质料,遇到了很多问题,于是今天就根据自己遇到的问题做些笔记
先贴出Google给出的Demo地址 https://github.com/cepr/android-serialport-api

踩坑第一步:

需要配置NDK,我百度一番,搜索了很多文章,大致配置差不多,参照的博客如下:
https://blog.youkuaiyun.com/weixin_44151070/article/details/100627791
第一步下载LLDB,NDK,CMake
在这里插入图片描述
第二步配置DNK路径
在这里插入图片描述

第三步 在环境变量中操作了一番,最后doc命令行中编译的结果如图所示:
在这里插入图片描述
在这里算是告诉我配置NDK成功了
但是这些好像并没有什么用处,之前查询的博客是说通过DNK重新编译so文件来适应最新的Android studio版本,我现在的是studio是3.53,targetSdkVersion 是29,按博客上说的是不能与现在的兼容的
所以我按照博客的去编译发现无法实现,总是各种报错,并没有成功

我查阅了这篇博客后,看到了曙光
Android 串口开发(一) 串口读写操作
他将串口代码封装了一下,我下载下来后第一次运行发现是不可以的,我换了台设备在次运行,发现可以了,后来使用了我自己的Demo,发现也是可以的,这里暴露了我的第一个问题:
1.硬件需要支持串口的输出(我之前使用的是商米设备,现在使用的是新大陆的设备)
2.需要知道当前硬件的准确地址与波特率
如果在不知道准确地址与波特率的情况下运行,会报如下错误:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mydemo/com.example.mydemo.ThreeActivity}: java.lang.SecurityException
这个安全异常是源码中本身抛出的

看到这里我就很开心了,那我的Demo在OK之前还有一个小问题,那就是报名问题
在这里插入图片描述
图中圈出来的地方是我最开始放SerialPort.java与SerialPortFinder.java文件的地方
当时运行时报了如下错误
No implementation found for java.io.FileDescriptor com.example.mydemo.serial.SerialPort.open(java.lang.String, int, int) (tried Java_com_example_mydemo_serial_SerialPort_open and Java_com_example_mydemo_serial_SerialPort_open__Ljava_lang_String_2II)
告诉我报名错误,当我更换了包名为google原来包名后,发现已经可以了

总结下运行条件:
1.硬件需要支持串口的输出
2.需要知道当前硬件的准确地址与波特率
3.包名要为android_serialport_api

使用
第一步:
将jniLibs放到java文件夹下面同时将so文件拷贝过来
在这里插入图片描述
第二步
将java文件拷贝到android_serialport_api文件中
在这里插入图片描述
第三步 在defaultConfig中引用jni文件
ndk {
abiFilters “armeabi”, “armeabi-v7a”, “x86” //u的类型
}
sourceSets {
main {
jni.srcDirs = []
}
}

Demo中的代码:

public class ThreeActivity extends AppCompatActivity {
private EditText et_set,et_get;
private SerialPort serialPort;
private SerialPortFinder serialPortFinder;
private InputStream inputStream;
private OutputStream outputStream;
private ReadThread mReadThread;

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

    et_set= (EditText) findViewById(R.id.et_set);
    et_get= (EditText) findViewById(R.id.et_get);

    serialPortFinder = new SerialPortFinder();
    String[] allDevicesPath = serialPortFinder.getAllDevicesPath();
    System.out.println("result-->"+allDevicesPath.length);
    try {
        serialPort = new SerialPort(new File("/dev/ttyHSL2"),115200,0);
        inputStream = serialPort.getInputStream();
        outputStream = serialPort.getOutputStream();
        mReadThread = new ReadThread();
        mReadThread.start();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
private class ReadThread extends Thread {
    @Override
    public void run() {
        super.run();
        while(!isInterrupted()) {
            int size;
            try {
                byte[] buffer = new byte[64];
                if (inputStream == null) return;
                size = inputStream.read(buffer);
                if (size > 0) {
                    String str =  new String(buffer);
                    System.out.println("result-->读取的文件"+str);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public void setData(View view){
}

public void getData(View view){
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (inputStream!=null){
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        inputStream=null;
    }

    if (outputStream!=null){
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        outputStream=null;
    }
  }
}

 /**
 * 向串口读取数据
 */
private void read() {
    try {
        Thread.sleep(1500);
        final byte[] bytes = new byte[1024 * 5];
        if (null != mInputStream) {
            int result = mInputStream.read(bytes);
            if (result > 0) {
                byte[] readBytes = new byte[result];
                System.arraycopy(bytes, 0, readBytes, 0, result);
                KmLog.d_log_file(TAG, "读取串口数据:------------" + new String(readBytes));
                parseResult(new String(readBytes));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        KmLog.d_log_file(TAG, "读取串口数据失败");
    }
}
//写入数据数据
private boolean write(byte[] data) {
    mSb.setLength(0);
    try {
        if (null != mOutputStream) {
            mOutputStream.write(data, 0, data.length);
            mOutputStream.flush();
        }
    } catch (Exception e) {
        KmLog.d_log_file(TAG, "写入串口数据失败");
        return false;
    }
    return true;
}

以上主要是业务代码,这里主要是操作流对象,没有什么特别的,但是一定要注意,如果有多个串口一定要记得关闭串口,否则会串口占用的哦

public class SerialPortUtils {
    protected OutputStream mOutputStream;
    private InputStream mInputStream;
    private ReadThread mReadThread;
    //串口秤连接成功标志
    public boolean isConnected = false;
    public SerialPortFinder mSerialPortFinder;
    private SerialPort mSerialPort = null;
    public String weight = "0";

    //单利
    public static SerialPortUtils mSerialPortUtils;

    public static SerialPortUtils getInstance() {
        if (mSerialPortUtils == null) {
            mSerialPortUtils = new SerialPortUtils();
        }
        return mSerialPortUtils;
    }

    /**
     * 初始化称
     */
    public void initSerialPortWeight(String path) {
        if (mSerialPort == null) {
            try {
                mSerialPortFinder = new SerialPortFinder();
                //D2mini,T1,T1mini:机器底座串口的节点路径/dev/ttyHSL1,
                //T2,S2,T2lite,X2,T2mini:/dev/ttyHSL3,
                mSerialPort = new SerialPort(new File(path), 9600, 0);
                mOutputStream = mSerialPort.getOutputStream();
                mInputStream = mSerialPort.getInputStream();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mReadThread = new ReadThread();
        mReadThread.start();
    }

    /**
     * 获取数据地址
     */
    public String[] getListPath() {
        if (mSerialPortFinder != null) {
            String[] allDevicesPath = mSerialPortFinder.getAllDevicesPath();
            return allDevicesPath;
        }
        return null;
    }

    /**
     * 读取数据
     */
    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (!isInterrupted()) {
                int size;
                try {
                    byte[] buffer = new byte[30];
                    if (mInputStream == null) {
                        break;
                    }

                    size = mInputStream.read(buffer);
                    if (size > 0) {
                        if (buffer[0] != (byte) 0x00) {
                            //如果byte不为0,还能通过就显示成功了
                            isConnected = true;
                            getWeightData(buffer, size);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        }
    }


    private void getWeightData(byte[] buffer, int size) {
        //将串口数据转换为字符串
        String temp = new String(buffer, 0, size);
//        Log.d("TAG", "getWeightData: "+temp);
        if (!TextUtils.isEmpty(temp)) {
            //去除串口两侧的空格  转换后的数据     "   0105   000  000\n\t"
            String trim = temp.trim();
            //去除多个空格合成一个
//            trim = trim.replaceAll(" {2,}"," ");
            //将其分为三等份
            String data[] = trim.split(" ");
            String datum = data[0];
            if (!TextUtils.isEmpty(datum) && datum.length() > 3) {
                if (mLinster != null) {
                    weight = getStringDate(datum, 0);
                    mLinster.onDataReceived(isConnected, weight, "", "");
                }
            }
        }
    }

    /**
     * 转为重量数据
     *
     * @param data
     * @param flag_realDate 0 为重量 1 为单价 和总价
     * @return
     */
    private String getStringDate(String data, int flag_realDate) {
        int temp = 0;

        try {
            temp = Integer.parseInt(data);
        } catch (Exception e) {
            return "0";
        }
        int div = 1000;
        if (flag_realDate != 0) {
            div = 100;
        }
        float tempDiv = divFloat(temp, div, 3);
        String str = String.valueOf(tempDiv);
        if (flag_realDate == 0) {
            if (str.length() < 5) {
                str = str + "0";
            } else if (str.length() < 4) {
                str = str + "00";
            }
        }
        return str;
    }

    /**
     * 提供除法計算
     * @param v1
     * @param v2
     * @return
     */
    public float divFloat(int v1, int v2, int scale) {
        BigDecimal b1 = new BigDecimal(Integer.toString(v1));
        BigDecimal b2 = new BigDecimal(Integer.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).floatValue();
    }

    private OnDataReceivedLinster mLinster;

    public interface OnDataReceivedLinster {
        void onDataReceived(boolean flag, String weight, String singlePrice, String totalPrice);
    }

    public void setOnDataReceivedLinster(OnDataReceivedLinster linster) {
        this.mLinster = linster;
    }

    /**
     * 初始化话或者结束都要干掉串口与线线程
     */
    public void onDestroy() {
        weight = "0.000";
        if (mSerialPort != null) {
            mSerialPort.close();
            mSerialPort = null;
        }
        if (mReadThread != null) {
            mReadThread.interrupt();
            mReadThread = null;
        }
        isConnected = false;

        if (mSerialPortUtils!=null){
            mSerialPortUtils = null;
        }
    }

在此处需要将google中的内容拷贝过来在此处需要将google中的内容拷贝过来

    defaultConfig {
        applicationId "com.XXX"
        minSdkVersion 19
        targetSdkVersion
        versionCode 1
        versionName "1.0.1.001"

        /**
         * [main].[sub].[revision].[build]
         * main : 主要版本号,标记产品版本变化,表示产品增加功能模块或者整体结构发生变化
         * sub :次要版本号,标记产品的已有的某个功能产生变化。
         * revision :修订号,标记产品功能的小变化、修正bug。
         * build :产品生成序号,每次build自动加1
         */
        javaCompileOptions {
            annotationProcessorOptions {
                includeCompileClasspath = true
            }
        }
        //矢量图加载支持包
        vectorDrawables.useSupportLibrary true

        ndk {
            // 声明创建指定cpu架构的so库, 不声明的话, 默认(gradle 1.5.0)会生成4中架构 多一种mips架构
            // 具体cpu架构的区别请参考:
            // for detailed abiFilter descriptions, refer to "Supported ABIs" @
            // https://developer.android.com/ndk/guides/abis.html#sa
            abiFilters "armeabi-v7a"
        }

        ndk {
            moduleName "serial_port"                      //so文件,lib+moduleName+。so
            abiFilters "armeabi", "armeabi-v7a", "x86"          //u的类型
        }

        ndk {
            //声明启用Android日志, 在c/c++的源文件中使用的#include <android/log.h> 日志将得到输出
            ldLibs "log"
        }
    }

sourceSets {
        main {
            jni.srcDirs = []
        }
    }

先写到这里,如果疑问请留言或者添加QQ1335745248 并留言“串口交流”,我们一起学习吧

android 串口驱动源代码 package android.serialport; 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; import android.util.Log; 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) 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("/system/bin/su"); /*String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";*/ String cmd = "chmod 777 /dev/s3c_serial0" + "\n" + "exit\n"; su.getOutputStream().write(cmd.getBytes()); if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { throw new SecurityException(); } } catch (Exception e) { e.printStackTrace(); throw new SecurityException(); } } mFd = open(device.getAbsolutePath(), baudrate); 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); public native void close(); static { System.loadLibrary("serial_port"); } }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值