前言
因为平时工作是基于串口通信开发,之前群里有人问串口通信怎么搞,正好自己也想总结一下平时开发经验,便准备写几篇关于串口通信的文章。
简单介绍
1. 串口概念
串口是一种用于在计算机和其他设备之间传输数据的通信接口。串口通常是通过一对传输数据的线来实现通信的,其中一条线传输数据(称为数据线),另一条线传输控制信号(称为控制线)。串口通常使用串行通信协议来传输数据,其中每个字节按照位的顺序一个接一个地传输。串口常用于连接计算机与外部设备,如打印机、调制解调器、传感器等。串口的主要特点是通信速度比较慢,但传输距离可以很长。常见的串口标准有RS-232、RS-485、TTL等。
2. 常用串口
Android常用串口大多都是RS-485与RS-232,下面是他们两之间的区别
- 传输距离:RS232通常使用单工或半双工通信方式,数据传输距离较短(一般不超过50英尺),通常只能用于单台主机和单台从机之间的通信;而RS485支持全双工通信方式,可实现多台主机和从机之间的通信,通信距离可达4000英尺以上。
- 对点通信:RS232一般只能实现点对点通信,即单个发送器和单个接收器之间的通信。而RS485支持多点通信,可以同时连接多个设备,实现多个发送器和多个接收器之间的通信。
- 传输速率:RS232通常的传输速率比RS485要慢,通常在115200 bps以下;而RS485支持更高的传输速率,可达到10 Mbps以上。
3. 应用场景
多为智能硬件这类的,Android起一个中间站这类的角色。
串口参数
1. 代码获取设备串口列表
常规获取
代码语言:javascript复制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[] getAllDevicesList() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while(itdriv.hasNext()) {
Driver driver = itdriv.next();
for (File file : driver.getDevices()) {
String device = file.getAbsolutePath();
devices.add(device);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
如果设备已经被Root的话可以试试下面这个,非Root的用这个方法会导致应用卡顿并且获取失败
代码语言:javascript复制private List<String> getDevTtyList() {
try {
String getRes = performSuCommandAndGetRes("ls /dev/tty*");
ArrayList<String> list = new ArrayList<>(Arrays.asList(getRes.split("/dev/")));
list.remove("");
return list;
} catch (Exception e) {
MyLogger.e(e, e.getMessage());
return null;
}
}
public static String performSuCommandAndGetRes(String cmd)
throws InterruptedException {
int repeatTimes = 5;
for (int i = 0; i < repeatTimes; i ) {
String result = "";
DataOutputStream dos = null;
DataInputStream dis = null;
Process p = null;
try {
p = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(p.getOutputStream());
dis = new DataInputStream(p.getInputStream());
dos.writeBytes(cmd "; echo "suCmdRes="$?n");
dos.flush();
dos.writeBytes("exitn");
dos.flush();
String line = "";
BufferedReader din = new BufferedReader(new InputStreamReader(
dis));
while ((line = din.readLine()) != null) {
result = line;
}
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (p != null) {
try {
p.destroy();
} catch (Exception e2) {
}
}
}
Log.i("sudo", cmd " = " result);
if (result != null && result.contains("suCmdRes") == true) {
for (int j = -5; j < 5; j ) {
result = result.replace("suCmdRes=" j, "");
}
return result;
} else {
if (i == repeatTimes - 1)
break;
Thread.sleep(1500);
}
}
return null;
}
2. 串口波特率(Baud Rate)
串口通信中的波特率是指每秒钟可以传输的比特数,通常用单位"波特"(baud)表示,也就是 "bit per second"(bps)。比如,波特率为9600,则表示每秒钟可以传输9600个bit。 在实际的串口通信中,发送端和接收端必须采用相同的波特率才能正常通信。如果发送端和接收端的波特率不一致,数据将无法正常解析,导致通信失败。
波特率我们一般与硬件对好就行,直接代码写死。如果要获取当前波特率可以调用SerialPort类下的getBaudRate()方法来获取当前串口的波特率。也可以使用adb shell指令
3. 数据位(Data Bit)
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。
4. 停止位(Stop Bit)
停止位是指一个数据帧的最后一位,用于表示一个数据帧的结束。通常情况下,停止位的值为1或2,表示一个或两个位的长度。例如,如果停止位的值为1,则一个数据帧的最后一个位为1,表示一个数据帧的结束。
5. 奇偶校验位(Parity Bit)
奇偶校验位是用于检验串口通信中数据传输是否正确的一种校验方式。奇偶校验位可以是奇校验位(Odd Parity)或偶校验位(Even Parity),其值为0或1。奇偶校验的基本原理是通过在数据位之后增加一个奇偶校验位,使得每个数据帧的总位数为奇数或偶数,从而检验传输数据的正确性。例如,假设一个数据帧的数据位为8,奇偶校验位为奇校验位,则在发送端发送数据时,校验位的值为1,使得整个数据帧的位数为奇数。在接收端接收数据时,如果接收到的数据帧位数不是奇数,则说明数据传输出错,需要重新发送。
6. VTIME 和 VMIN
VTIME: 指的是串口通信的等待时间,单位是十分之一秒(0.1秒)。当读取串口数据时,如果缓冲区中没有数据,程序会等待 VTIME 时间,如果 VTIME 时间到了还没有数据,程序就会返回读取失败。如果 VTIME 的值为 0,则表示不等待。 VMIN: 指的是串口通信读取的最小字节数。当读取串口数据时,如果缓冲区中的数据字节数不足 VMIN,程序就会等待直到数据字节数达到 VMIN 或超时。如果 VMIN 的值为 0,则表示不等待。 举个例子,如果 VTIME=5,VMIN=10,表示程序会等待 0.5 秒,如果 0.5 秒内读取到了 10 个字节的数据,就返回成功;如果 0.5 秒内没有读取到 10 个字节的数据,就返回失败。 VTIME 和 VMIN 参数的具体值需要根据实际情况进行设置,通常需要结合串口设备的波特率、数据位、停止位、奇偶校验等参数来确定。如果设置不当,可能会导致数据读取不完整、读取超时等问题。
简单使用
1. 添加依赖
代码语言:javascript复制implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0'
2. 打开,监听,关闭
代码语言:javascript复制import android.serialport.SerialPort;
import android.serialport.SerialPortCallback;
import android.serialport.SerialPortFinder;
import java.io.IOException;
public class SerialPortDemo {
private SerialPort mSerialPort;
private SerialPortCallback mCallback;
// 串口配置参数
private String mDevicePath = "/dev/ttyS1"; // 串口设备路径
private int mBaudRate = 9600; // 波特率
private int mDataBits = 8; // 数据位
private int mStopBits = 1; // 停止位
private int mParity = 0; // 奇偶校验位
public void openSerialPort() {
try {
// 打开串口
mSerialPort = new SerialPort(mDevicePath, mBaudRate, mDataBits, mStopBits, mParity);
// 创建串口回调
mCallback = new SerialPortCallback() {
@Override
public void onDataReceived(byte[] buffer, int size) {
// 处理接收到的数据
String data = new String(buffer, 0, size);
System.out.println("Received data: " data);
}
@Override
public void onSerialPortClosed() {
// 串口已关闭
System.out.println("Serial port closed.");
}
@Override
public void onError(Exception e) {
// 发生异常
e.printStackTrace();
}
};
// 设置串口回调
mSerialPort.setCallback(mCallback);
// 启动监听
mSerialPort.start();
System.out.println("Serial port opened.");
} catch (IOException e) {
e.printStackTrace();
}
}
public void closeSerialPort() {
if (mSerialPort != null) {
// 关闭串口
mSerialPort.stop();
// 释放资源
mSerialPort = null;
mCallback = null;
}
}
}
参考
github.com/licheedev/A…