安卓开发板之串口通信,通过modbus Rtu协议控制下位机
1.环境准备
1).使用该类需要将两个文件夹拷贝到项目目录中,这两个文件夹分别是jni,和jniLibs(jni就是Java本地接口,使用它可以实现java语言与其他语言的交互)jni和jniLibs的压缩包
2)配置app文件夹下的bulid.gradle文件(配置好后重新编译一次)
3.配置好调整到安卓特有的目录结构会看到,这说明环境准备完成
2.编写串口操作核心类
该类的用来控制串口的打开和关闭,并获取到对应串口的输入输出流对象。
package android_serialport_api;
import android.util.Log;
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;
public class SerialPort {
private static final String TAG = "SerialPort";
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
//检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限
if (!device.canRead() || !device.canWrite()) {
try {
//通过挂在到linux的方式,修改文件的操作权限
Process su = Runtime.getRuntime().exec("/system/xbin/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.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(调用java本地接口,实现串口的打开和关闭)
/**串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位
其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1*/
/**
* @param path 串口设备的据对路径
* @param baudrate 波特率
* @param flags 校验位
*/
private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();
static {//加载jni下的C文件库
System.loadLibrary("serial_port");
}
}
3.编写测试类
(注意:红字代码只是本人在MyEclipse中调试所用,无需拷贝)
串口的打开和关闭很好实现,输入输出数据也很好实现,
关键的问题是输入的数据包下位机总是解析不出来,后来摸索了好久
终于发现问题是我发送的数据包的格式有问题。
1.首先我们要知道上位机发送给下位机的到底是什么格式的
(我就以控制从站号为1的从机的第一个线圈的开关命令为例)
开和关的16进制命令分别为:
开启第一个线圈:01 05 00 00 FF 00 8C 3A
关闭第一个线圈:01 05 00 00 00 00 CD CA
上面的命令是平常在主从机调试软件中输入的,但是下位机实际接收到的格式却并非如此)
2.首先我找了一个上位机(这里我用的是有人的网关,可以通过网页界面控制 通过rj485口
向下位机发送数据),配合usb转串口调试工具,以及串口调试软件 SSCOM32.EXE
我终于看到了这个命令的本来面目:“ ?”
what?这是什么?对,我也想问。还好SSCOM32.EXE可以选择以16进制格式显示选项
于是我就看了这个:01 05 00 00 FF 00 8C 3A。对没错,终于看到认识的了。
但是它内部是怎么实现的呢?
于是我用在网上找的字符串转16进制字符串方法
/**
* 字符串转换成十六进制字符串
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
}
return sb.toString();
}
调用一下:
@Test
public void run1() {
// TODO Auto-generated method stub
String string1=Hex2Bytes.str2HexStr(" ?");
System.out.println(string1);
}
于是看到了下面的结果
看起来挺像的不过中间的空格没有了
(为什么和去除空格之后的不完全一样,可能是代码问题,所以上面的代码“字符串
转换成十六进制字符串的”不建议拷贝)
顺着这条线我们继续往下走
于是我将命令:改成:01050000FF008C3A
当然你也可以用:"01 05 00 00 FF 00 8C 3A".trim();去除空格。
然后代码就变成了下面的样子(结果大功告成!!!)
测试类的真正面目(注意每个安卓开发板的串口名不一样,我的用的是百度人脸识别开发板485串口)
package com.serial_test;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import android_serialport_api.SerialPort;
public class SerialTest {
protected SerialPort mSerialPort;
protected OutputStream mOutputStream;
private static String prot = "ttysWK0";
private static int baudrate = 9600;
public void openDoor() {
new Thread() {
@Override
public void run() {
try {
mSerialPort=new SerialPort(new File("/dev/" + prot), baudrate,0);
mOutputStream = mSerialPort.getOutputStream();
//mInputStream = mSerialPort.getInputStream();
//mOutputStream = mSerialPort.getOutputStream();
byte [] bytes1=HexStrToBytes.hexToByteArray("01050000FF008C3A");
mOutputStream.write(bytes1,0,bytes1.length);
Thread.sleep(1000*2);
byte [] bytes2=HexStrToBytes.hexToByteArray("010500000000CDCA");
mOutputStream.write(bytes2,0,bytes2.length);
Log.i("test", "发送成功");
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
Log.i("test", "打开失败");
e.printStackTrace();
}catch (Exception e){
Log.i("test", "发送失败");
}
finally {
if (mSerialPort != null) {
mSerialPort.close();
}
if (mOutputStream != null) {
try {
mOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
}
16进制字符串与字节数组的转换工具类
package com.serial_test;
public class HexStrToBytes {
/**
* Hex字符串转byte
* @param inHex 待转换的Hex字符串
* @return 转换后的byte
*/
public static byte hexToByte(String inHex){
return (byte)Integer.parseInt(inHex,16);
}
/**
* 16进制字符串转换为字节数组
*/
public static byte[] hexToByteArray(String inHex){
int hexlen = inHex.length();
byte[] result;
if (hexlen % 2 == 1){
//奇数
hexlen++;
result = new byte[(hexlen/2)];
inHex="0"+inHex;
}else {
//偶数
result = new byte[(hexlen/2)];
}
int j=0;
for (int i = 0; i < hexlen; i+=2){
result[j]=hexToByte(inHex.substring(i,i+2));
j++;
}
return result;
}
}