上半年最好的Android串口开发入门指南

特别说明

当前博客平台账号已废弃,如果有使用细节问题请前往我新博客平台进行讨论交流。

个人博客平台 HuRuWo的技术小站

文章首发于个人博客HuRuWo的技术小站,如果本文非vip用户无法完全浏览或者图片无法打开,可前往个人博客文章地址查看文章并留言讨论。

个人博客文章地址上半年最好的Android串口开发入门指南)

更多技术文章访问本人博客HuRuWo的技术小站,包括 Electron从零开发 Android 逆向 app 微信数据抓取 抖音数据抓取 闲鱼数据抓取 小红书数据抓取 其他软件爬虫 等技术文章

一些预备知识

物联网开发开发是时下热门的行业。Android系统自然也能进行物联网开发。除开Android本身自带的模块还有一类通过外部链接的设备需要通过串口来进行通信。本人在做完两个相关的抓娃娃和寄存柜项目之后觉得需要总结一点东西给大家。

关于串口

串口通信指串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。

在串口通信中,常用的协议包括RS-232、RS-422和RS-485。

当然具体是那种协议和你选择的硬件有关,将你的硬件插到对应协议的串口口即可。

开发前的准备

1.检查你的开发板设备,包括开发板信息,开发板上面包含的模块信息。是否有Wifi模块 蓝牙模块 指定接口等。还有一方面就是关于开发板系统的信息,开发板的系统版本。如果需要特别定制,可以和厂商商量。

关于系统定制
某些特殊的板块需要隐藏状态栏不能被下拉,否则会被退出应用。还有一方面就是可以定制取消掉下导航栏。

2.检查你的硬件装备
正确连接你的设备,向你的硬件提供商索要开发资料。基本的资料包括硬件的通讯命令格式。当然更好的是如果能要到开发程序资料。比如android程序或者源码那就更好了。

3.正确的连接,测试你的硬件与系统
Android串口助手
下载一个串口调试助手,按照资料输入命令。测试是否能够成功的启动设备。并且收到对应的返回数据。

开发阶段

需要一点点的JNI知识和一点点Android多线程开发经验

整体的开发流程如下:打开指定串口-->开启接收数据线程(readthead)-->发送串口数据-->接收数据处理返回信息-->关闭接收数据线程(readthead)-->关闭串口。

导入so库

谷歌开源serialPort api项目

里面封装了c层代码调用底层代码的通信方式,如果你们喜欢改东西的话。可以自己改着玩,不过我觉得没有必要,因为这些代码已经封装的很好了。直接使用即可。

至于通过c代码如何生成相应的so文件,以及如何java层调用c层代码都是很基础的东西啦。
我不想在这里展开大篇幅的讲JNI,因为串口通信其实用的JNI知识不多。

首先把JNI相关代码导入到自己的工程里面:

先看下目录结构吧:

jni目录

[图片上传失败...(image-76edb2-1606101414169)]

java 目录

[图片上传失败...(image-df6e96-1606101414169)]

SerialPort.java

了解JNI的同学都知道的,这个SerialPort.h对应的就是SerialPort.java层的native 方法。

这里用两个方法

private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();

很显然一个是打开串口 一个是 关闭串口 方法

打开串口之前,程序需要获得最高权限,SerialPort.java的构造函数里面需要获得设备的超级root权限,也是通过输入su命令完成。

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 666 " + 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.printStackTrace();
                throw new SecurityException();
            }
        }

最后记得调用生成的.so文件

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

SerialPortFinder

这个类很简单,能用于获取设备的串口信息。通常一个开发板会有几个到十几个的串口。
两个public方法:

  • public String[] getAllDevices() 获取所有串口名称
  • public String[] getAllDevicesPath() 获取所有串口地址

开始通信

[图片上传失败...(image-3ca0db-1606101414169)]

整个信息发送接收步骤如下:

1.初始化SerialPort 获得权限打开指定串口
2.打开ReadThread监听数据返回
3.使用SendThread发送数据
4.继续发送或者关闭

为此我们需要写一个SerialHelper来简化代码,以下是核心代码:

构造函数

 public SerialHelper(String sPort, int iBaudRate) {
        this.sPort = "/dev/ttyS3";
        this.iBaudRate = 9600;
        this._isOpen = false;
        this._bLoopData = new byte[]{48};
        this.iDelay = 500;
        this.sPort = sPort;
        this.iBaudRate = iBaudRate;
    }

打开 关闭 串口

//打开时打开监听线程
 public void open() throws SecurityException, IOException, InvalidParameterException {
        this.mSerialPort = new SerialPort(new File(this.sPort), this.iBaudRate, 0);
        this.mOutputStream = this.mSerialPort.getOutputStream();
        this.mInputStream = this.mSerialPort.getInputStream();
        this.mReadThread = new SerialHelper.ReadThread();
        this.mReadThread.start();
        this.mSendThread = new SerialHelper.SendThread();
        this.mSendThread.setSuspendFlag();
        this.mSendThread.start();
        this._isOpen = true;
    }

   // 关闭线程 释放函数
    public void close() {
        if (this.mReadThread != null) {
            this.mReadThread.interrupt();
        }

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

        this._isOpen = false;
    }

两个线程 发送线程:

private class SendThread extends Thread {
        public boolean suspendFlag;

        private SendThread() {
            this.suspendFlag = true;
        }

        public void run() {
            super.run();

            while(!this.isInterrupted()) {
                synchronized(this) {
                    while(this.suspendFlag) {
                        try {
                            this.wait();
                        } catch (InterruptedException var5) {
                            var5.printStackTrace();
                        }
                    }
                }

                SerialHelper.this.send(SerialHelper.this.getbLoopData());

                try {
                    Thread.sleep((long)SerialHelper.this.iDelay);
                } catch (InterruptedException var4) {
                    var4.printStackTrace();
                }
            }

        }

        public void setSuspendFlag() {
            this.suspendFlag = true;
        }

        public synchronized void setResume() {
            this.suspendFlag = false;
            this.notify();
        }
    }

读取线程

    private class ReadThread extends Thread {
        private ReadThread() {
        }

        public void run() {
            super.run();

            while(!this.isInterrupted()) {
                try {
                    if (SerialHelper.this.mInputStream == null) {
                        return;
                    }

                    byte[] buffer = new byte[512];
                    int size = SerialHelper.this.mInputStream.read(buffer);
                    if (size > 0) {
                        ComBean ComRecData = new ComBean(SerialHelper.this.sPort, buffer, size);
                        SerialHelper.this.onDataReceived(ComRecData);
                    }
                } catch (Throwable var4) {
                    Log.e("error", var4.getMessage());
                    return;
                }
            }

        }
    }

其他函数见代码,包括数值和文本发送 波特率的设置等等。

实战一个串口数据调试助手

下面使用封装的SerialHelper来完成整个数据发送接收:

界面随便搞一下:

[图片上传失败...(image-be31d6-1606101414169)]

然后开始逻辑代码:

首先实例化SerialPortFinder 实现数据接收 写入列表

serialPortFinder = new SerialPortFinder();
        serialHelper = new SerialHelper() {
            @Override
            protected void onDataReceived(final ComBean comBean) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getBaseContext(), FuncUtil.ByteArrToHex(comBean.bRec), Toast.LENGTH_SHORT).show();
                        logListAdapter.addData(comBean.sRecTime+":   "+FuncUtil.ByteArrToHex(comBean.bRec));
                        recy.smoothScrollToPosition(logListAdapter.getData().size());
                    }
                });
            }
        };

然后利用SerialPortFinder找到所有的串口号,列出来所有的波特率 ,都传给spinner

final String[] ports = serialPortFinder.getAllDevicesPath();
final String[] botes = new String[]{"0", "50", "75", "110", "134", "150", "200", "300", "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "576000", "921600", "1000000", "1152000", "1500000", "2000000", "2500000", "3000000", "3500000", "4000000"};

SpAdapter spAdapter = new SpAdapter(this);
        spAdapter.setDatas(ports);
        spSerial.setAdapter(spAdapter);

SpAdapter spAdapter2 = new SpAdapter(this);
        spAdapter2.setDatas(botes);
        spBote.setAdapter(spAdapter2)

打开串口:

btOpen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    serialHelper.open();
                    btOpen.setEnabled(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

数据发送(两种数据发送格式):

文本类型

 if (serialHelper.isOpen()) {
                            serialHelper.sendTxt(edInput.getText().toString());
                        } else {
                            Toast.makeText(getBaseContext(), "搞毛啊,串口都没打开", Toast.LENGTH_SHORT).show();
                        }

Hex类型

if (serialHelper.isOpen()) {
                            serialHelper.sendHex(edInput.getText().toString());
                        } else {
                            Toast.makeText(getBaseContext(), "搞毛啊,串口都没打开", Toast.LENGTH_SHORT).show();
                        }

最后记得关闭一下串口咯:

 @Override
    protected void onDestroy() {
        super.onDestroy();
        serialHelper.close();
    }

好的 ,完事了。 测试一下


连线开机:

[图片上传失败...(image-82778a-1606101414169)]

发串口信息:

[图片上传失败...(image-b8ebf1-1606101414169)]

同时设备也滴塌滴塌的响了,完美。

代码我也放上去把Android串口助手

一些要说的

虽然整个JNI移植过程非常简单,但是问题出现了。如果大家使用的3.0版本的AS、会发现默认的JNI使用Cmake而不是.mk文件配置的。

所以又增加了一个难度,为了方便大家。我把所有关于串口的资源打包成aar 文件,大家直接使用即可。

谷歌android串口开发 aar文件

使用过程:
aar文件导入lib文件夹
gradle文件

 repositories {
        flatDir {
            dirs 'libs'
        }
    }

dependencies {
    implementation(name: 'serialport-1.0.1', ext: 'aar')
}

完成。

总结一下

基本的串口通信到此结束。到了实际生产,更多的要解决多线程上的逻辑问题。设备的各种状态以及突发状况的处理等等。所以串口通信成功只是一个小小的开始,更多的问题还在后面。

再放一次本篇文章的代码 Android串口助手

再次编辑一下:

!this.isInterrupted() 这个方法是谷歌官方demo里面的代码,我没有做修改。当然实际生产的时候你会碰到多个发送界面问题 需要结合 available 方法判断是否有数据

try {
while (inputStream.available() > 0) {
int numBytes = inputStream.read(readBuffer);
 }
System.out.print(new String(readBuffer));
} catch (IOException e) {
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容