Android串口通信(一)
前言
做了一些Android驱动板的串口通信,对控制卡,继电器开关,麦克风,PWM风机等进行操作,进行一下记录分享。其中,包含了Android自身的串口操作及
Usb
转串口的操作。本篇主要介绍串口基础内容和基于谷歌官方android-serialport-api库 对Android设备进行的串口操作,下一篇中将给出基于Usb转串口驱动库usb-serial-for-android 的相关内容及操作。
串口通信
串行接口是一种可以将接收来自CPU的并行数据字符转换为连续的串行数据流发送出去,同时可将接收的串行数据流转换为并行的数据字符供给CPU的器件。
一般完成这种功能的电路,我们称为串行接口电路。
串口按位(bit)发送和接收字节的通信方式即串口通信。尽管比按 字节 (byte)的 并行通信 慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信(RS232是要用在近距离传输上最大距离为30M,RS485用在长距离传输最大距离1200M)。通信使用3根线完成,分别是地线、发送、接收。
串口参数
串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配。
波特率
这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。一般调制速率大于波特率,比如曼彻斯特编码)。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
奇偶校验位
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
串口开发
在Android开发中,对串口数据的读取和写入,实际上是是通过I/O流读取,写入文件数据。串口使用完毕需要关闭(文件关闭)。串口关闭,即是文件流关闭。
开发流程
- 获取设备串口地址;
- 配置(波特率,校验位等),建立指定串口通信;
- 串口写入及接收返回的数据;
- 结束通信,串口关闭。
使用过程
基于谷歌官方android-serialport-api 编译修改,主要包含SerialPortFinder
和SerialPort
,进行串口地址的获取和串口内容的开启、写入、读取及关闭。
- 通过
SerialPortFinder
获取所有串口地址,进行串口的选取(使用中通常来说如果插入Usb
串口设备如:USB转485/442
、USB转TTL串口线
,会显示为/dev/ttyUSB0
之类的串口地址。);
public class SerialPortFinder {
public class Driver {
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}
private String mDriverName;
private String mDeviceRoot;
Vector<File> mDevices = null;
Vector<File> getDevices() {
if (mDevices == null) {
mDevices = new Vector<>();
File dev = new File("/dev");
File[] files = dev.listFiles();
if (files == null) {
return mDevices;
}
int i;
for (i = 0; i < files.length; i++) {
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
Log.d(TAG, "Found new device: " + files[i]);
mDevices.add(files[i]);
}
}
}
return mDevices;
}
public String getName() {
return mDriverName;
}
}
private static final String TAG = "SerialPort";
private Vector<Driver> mDrivers = null;
Vector<Driver> getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector<Driver>();
LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers"));
String l;
while ((l = r.readLine()) != null) {
// Issue 3:
// Since driver name may contain spaces, we do not extract driver name with split()
String drivername = l.substring(0, 0x15).trim();
String[] w = l.split(" +");
if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length - 4]);
mDrivers.add(new Driver(drivername, w[w.length - 4]));
}
}
r.close();
}
return mDrivers;
}
public String[] getAllDevices() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while (itdev.hasNext()) {
String device = itdev.next().getName();
String value = String.format("%s (%s)", device, driver.getName());
devices.add(value);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
public String[] getAllDevicesPath() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while (itdev.hasNext()) {
String device = itdev.next().getAbsolutePath();
devices.add(device);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
}
-
核心库
libserial_port.so
加载,通过使用谷歌官方android-serialport-api库 libs中已经编译生成的libserial_port.so
文件或对jni
文件夹下c文件进行重新编译生成所需,不要忘记在.gradle
文件中配置jni
路径。sourceSets { main { jniLibs.srcDirs = ['libs'] } }
-
选取串口地址后,结合比特率及标志位,即可通过已加载核心库
libserial_port.so
进行串口打开操作;public class SerialPort { private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudRate, int flags) throws SecurityException, IOException { mFd = open(device.getAbsolutePath(), baudRate, flags); if (mFd == null) { throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } private native static FileDescriptor open(String path, int baudrate, int flags); public native void close(); static { System.loadLibrary("serial_port"); } }
-
编写的控制类,统一进行串口地址获取,打开,读取,写入及关闭操作:
public class SerialController {
private ExecutorService mThreadPoolExecutor = Executors.newCachedThreadPool();
private InputStream inputStream;
private OutputStream outputStream;
private boolean isOpened = false;
private OnSerialListener mOnSerialListener;
/**
* 获取所有串口路径
*
* @return 串口路径集合
*/
public List<String> getAllSerialPortPath() {
SerialPortFinder mSerialPortFinder = new SerialPortFinder();
String[] deviceArr = mSerialPortFinder.getAllDevicesPath();
return new ArrayList<>(Arrays.asList(deviceArr));
}
/**
* 打开串口
*
* @param serialPath 串口地址
* @param baudRate 波特率
* @param flags 标志位
*/
public void openSerialPort(String serialPath, int baudRate, int flags) {
try {
SerialPort serialPort = new SerialPort(new File(serialPath), baudRate, flags);
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
isOpened = true;
if (mOnSerialListener != null) {
mOnSerialListener.onSerialOpenSuccess();
}
mThreadPoolExecutor.execute(new ReceiveDataThread());
} catch (Exception e) {
if (mOnSerialListener != null) {
mOnSerialListener.onSerialOpenException(e);
}
}
}
/**
* 关闭串口
*/
public void closeSerialPort() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
isOpened = false;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送串口数据
*
* @param bytes 发送数据
*/
public void sendSerialPort(byte[] bytes) {
if (!isOpened) {
return;
}
try {
if (outputStream != null) {
outputStream.write(bytes);
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 返回串口是否开启
*
* @return 是否开启
*/
public boolean isOpened() {
return isOpened;
}
/**
* 串口返回数据内容读取
*/
private class ReceiveDataThread extends Thread {
@Override
public void run() {
super.run();
while (isOpened) {
if (inputStream != null) {
byte[] readData = new byte[1024];
try {
int size = inputStream.read(readData);
if (size > 0) {
if (mOnSerialListener != null) {
mOnSerialListener.onReceivedData(readData, size);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 设置串口监听
*
* @param onSerialListener 串口监听
*/
public void setOnSerialListener(OnSerialListener onSerialListener) {
this.mOnSerialListener = onSerialListener;
}
/**
* 串口监听
*/
public interface OnSerialListener {
/**
* 串口数据返回
*/
void onReceivedData(byte[] data, int size);
/**
* 串口打开成功
*/
void onSerialOpenSuccess();
/**
* 串口打开异常
*/
void onSerialOpenException(Exception e);
}
}
以上就是本篇主要介绍的串口通信基础知识及Android串口内容调用。除了Google官方驱动库外,我们实际操作中可能还需要用到Usb
转串口内容的操作,将在下篇给出具体内容。
访问Github
项目查看具体代码实现:
https://github.com/MickJson/AndroidUSBSerialPort
参考资料:
百度百科串口通信:https://baike.baidu.com/item/串口通信
谷歌串口通信库:https://github.com/cepr/android-serialport-api
欢迎点赞/评论,你们的赞同和鼓励是我写作的最大动力!