前言:
最近在总是看见有人在群里面问一些串口通信相关的问题,特别是对于我们这些做APP出生的程序员来说,初次接触串口通信的确是会遇到各种Bug,各种摸不着头脑。串口通信偏向嵌入式一点,是Android设备通过串口与其他设备进行通信的一种方式,本文介绍的Android纯串口的通信,并不是手机上的USB串口通信。
简介:
首先简述一下此项目应用,它是一个简单的物联网智能盒子,主要工作:是通过Android开发板上的串口进行数据读写操作。一块Android开发板外接一个单片机,单片机上面跑有MQTT服务与后台服务器通信,单片机收到MQTT发布的不同信息后,将信息转换成不同的串口指令发给Android板,Android板收到对应的串口指令就做相应的界面处理并发送响应指令给单片机,然后单片机直接控制硬件设备工作。串行端口 (SerialPort)简称:串口,主要用于数据被逐位按顺序传送的通讯方式称为串口通讯(简单来讲就是按顺序一位一位地传输数据)。
常见的串口有25针和9针(遵循RS-232标准)
串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。
串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线(GND)、发送(TX)、接收(RX)。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
Android SDK并没有在Framework层实现封装关于串口通信的类库。但是,Android是基于Linux kernel 2.6上的,所以我们可以像在Linux系统上一样来使用串口。因为Framework层中并没有封装关于串口通信的类库,所以我们需要通过Android NDK来实现打开、读写串口,然后提供接口供JAVA本地调用。
这里可以参照Google已经给出了源码,地址在GitHub android-serialport-api
这是12年的代码,还是Eclipse工程,本文主要介绍如何在Android Studio中使用。这里先得配置好NDK环境
1:项目配置
首先看一下项目结构:创建了一个jni和jniLibs两个包,jni包用来放生成的头文件(.h文件)及Java本地方法的C代码实现类;jniLibs包用来放不同abi体系的so库
Java层的代码,Google已经给封装好了,主要的都在 SerialPort.java
第一步:拷贝创建 native方法的java类 SerialPort.java 到自己的项目中;
第二步:生成.h头文件; 生成方法有多种,这里使用 cd \app\src\main\java 进入到java目录下,然后 javah -classpath . -jni +全限定类名
第三步:实现SerialPort.java类中的本地方法,这里直接拷贝Google写好的C实现,将c代码中的函数名与生成的头文件中的函数名保持一致
配置build.gradle
串口作操都封装到SerialPortUtil类中
/**
* Created by Roy88 on 2017/6/18.
*
*串口工具类
*/
public classSerialPortUtil {
private static finalStringTAG= SerialPortUtil.class.getSimpleName();
public staticSerialPortserialPort=null;
public staticInputStreammInputStream=null;
public staticOutputStreammOutputStream=null;
public static booleanflag=false;
private staticBufferedReaderbr;
/**
* 打开串口
*/
public static voidopenSrialPort(String port, intbaudrate){
Log.i(TAG,"打开串口");
try{
serialPort=newSerialPort(newFile("/dev/"+ port),baudrate,0);
//获取打开的串口中的输入输出流,以便于串口数据的收发
mInputStream=serialPort.getInputStream();
mOutputStream=serialPort.getOutputStream();
flag=true;
//接收串口数据
receiveSerialPort();
}catch(IOException e) {
e.printStackTrace();
}
}
/**
* 接收串口数据
*/
public static voidreceiveSerialPort(){
newThread(newRunnable() {
@Override
public voidrun() {
//循环接收串口数据
while(flag) {
try{
if(mInputStream==null)return;
br=newBufferedReader(newInputStreamReader(mInputStream));
String str;
while((str =br.readLine()) !=null)
{
if(TextUtils.isEmpty(str))continue;
Log.i(TAG,"接收串口数据:"+ str);
if(String.valueOf(str.charAt(0)).equals("{") && str.substring(str.length() -1).equals("}")){
acceptAndNotify(str);
}
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* 区分收到的指令数据并分类分发
*
*@paramjsonBack收到的JSON指令
*/
private static voidacceptAndNotify(String jsonBack) {
if(jsonBack ==null||"".equals(jsonBack.trim()))
throw newIllegalArgumentException("JsonBack is illegal, please check args ... ");
JsonParser jsonParser =newJsonParser();
JsonObject json = (JsonObject) jsonParser.parse(jsonBack);
if(json ==null)
throw newJsonParseException("Json Parse error, please check args ... ");
String protocolResult = json.getAsJsonPrimitive("protocol").getAsString();
if(protocolResult ==null||"".equals(protocolResult))
throw newNumberFormatException("转化错误... ");
switch(protocolResult) {
case"coin_in":
// EventBus.getDefault().post(new SerialPortEvent(GsonUtil.parse(jsonBack, CoinAndRemoteCoinBean.class)));
break;
case"remote_coin_in":
// EventBus.getDefault().post(new SerialPortEvent(GsonUtil.parse(jsonBack, CoinAndRemoteCoinBean.class)));
break;
case"key_event":
// EventBus.getDefault().post(new SerialPortEvent(GsonUtil.parse(jsonBack, OrientationAndKeyBean.class)));
break;
}
}
/**
* 发送串口数据
*@paramdata要发送的数据
*/
public static voidsendSerialPort(String data){
Log.i(TAG,"发送串口数据:"+ data);
try{
byte[] sendData = data.getBytes();
mOutputStream.write(sendData);
mOutputStream.flush();
Log.i(TAG,"发送串口数据成功!");
}catch(IOException e) {
e.printStackTrace();
Log.i(TAG,"发送串口数据失败!");
}
}
/**
*关闭串口
*关闭串口中的输入输出流
*然后将flag的值设为flag,终止接收数据线程
*/
public static voidcloseSerialPort(){
Log.i(TAG,"关闭串口");
try{
if(serialPort!=null) {
serialPort.close();
}
if(mInputStream!=null) {
mInputStream.close();
}
if(mOutputStream!=null){
mOutputStream.close();
}
if(br!=null){
br.close();
}
flag=false;
}catch(IOException e) {
e.printStackTrace();
}
}
}
本文没有介绍关于jni、NDK的内容,因为上网有很讲这方面知识讲解。如有不了解的请自行百度、google。
注意:
不同的开发板操作的串口路径是不同的,比如我的开发板上的串口路径是“/dev/ttyS5”;还有就是数据传输的波特率要设置成一致,不然收到的数据会乱码。