一:背景
本文着重讲安卓下的串口。
由于开源的Android在各种智能设备上的使用越来越多,如车载系统等。在我们的认识中,Android OS的物理接口一般只有usb host接口和耳机接口,但其实安卓支持各种各样的工业接口,如HDMI、usb、网口、串口等等。
下图就是一块Android工业板,标圈的DB9(也叫RS232串口)就是串口中的一种形态。
二、什么是串口?
串行端口 ,即:SerialPort,简称串口,主要用于数据被逐位按顺序传送的通讯方式称为串口通讯(简单来讲就是按顺序一位一位地传输数据)。
三、串口的一般形态
串口一般有RS232和RS485之分,485串口可以使用RS-232转RS-485串口的转换器转换。
RS232:
232协议的串口是全双工 的,它允许数据同时接收和发送,但RS232的理论传输距离只有10米。
RS-485:
485是半双工的,半双工意味着同一时间只能收/发,就像是独木桥,同时只能有一个方向的人流通过,如果对向有来人则会造成数据丢失,RS485的理论距离是1200峭,通常如果要远距离使用的话会使用485串口,短距离则可以使用232。
四、串口的使用
无论是Android、windows还是Linux,串口的使用都要以下几步:
-
打开串口
-
串口配置(一般为:波特率、数据位、停止位和奇偶校验)
-
串口操作(读/写,无非就是输入输出流的操作罢了)
-
关闭串口
五、代码实践
package com.xz.andfasterserialport;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import tp.xmaihh.serialport.SerialHelper;
import tp.xmaihh.serialport.bean.ComBean;
import tp.xmaihh.serialport.stick.AbsStickPackageHelper;
import tp.xmaihh.serialport.utils.ByteUtil;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button leftReduce;
private Button leftAdd;
private Button buyWine;
private TextView wine_number;
private String yellowWineType = "01";
@BindView(R.id.rg_type)
RadioGroup mRgType;
private SerialHelper serialHelper;
private boolean isHexType = false;
private String text = "";
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
ComBean comBean = (ComBean) msg.obj;
String time = comBean.sRecTime;
String rxText;
rxText = new String(comBean.bRec);
if (isHexType) {
//转成十六进制数据
rxText = ByteUtil.ByteArrToHex(comBean.bRec);
}
text += "Rx-> " + time + ": " + rxText + "\r" + "\n";
//mEtReadContent.setText(text);
return false;
}
});
private RadioButton radioButton1;
private RadioButton radioButton2;
private RadioButton radioButton3;
private RadioButton radioButton4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initSerialConfig();
//下面的两行设置全屏,隐藏系统状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
leftReduce = findViewById(R.id.left_reduce);
leftAdd = findViewById(R.id.left_add);
buyWine = findViewById(R.id.buy_wine);
wine_number = findViewById(R.id.wine_number);
radioButton1 = findViewById(R.id.rb_mk);
radioButton2 = findViewById(R.id.rb_ls);
radioButton3 = findViewById(R.id.rb_lx);
radioButton4 = findViewById(R.id.rb_fzz);
leftReduce.setOnClickListener(this);
leftAdd.setOnClickListener(this);
buyWine.setOnClickListener(this);
radioButton1.setOnClickListener(this);
radioButton2.setOnClickListener(this);
radioButton3.setOnClickListener(this);
radioButton4.setOnClickListener(this);
}
private void initSerialConfig() {
//初始化SerialHelper对象,设定串口名称和波特率
serialHelper = new SerialHelper(Const.SPORT_NAME, Const.BAUD_RATE) {
@Override
protected void onDataReceived(ComBean paramComBean) {
Message message = mHandler.obtainMessage();
message.obj = paramComBean;
mHandler.sendMessage(message);
}
};
/*
* 默认的BaseStickPackageHelper将接收的数据扩展成64位,一般用不到这么多位
* 我这里重新设定一个自适应数据位数的
*/
serialHelper.setStickPackageHelper(new AbsStickPackageHelper() {
@Override
public byte[] execute(InputStream is) {
try {
int available = is.available();
if (available > 0) {
byte[] buffer = new byte[available];
int size = is.read(buffer);
if (size > 0) {
return buffer;
}
} else {
SystemClock.sleep(50);
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
});
}
//设置数据格式为十六进制还是其他,我们希望isHexType = true
@Override
protected void onDestroy() {
super.onDestroy();
serialHelper.close();
serialHelper = null;
}
//打开串口方法
public void openSerialPort() {
if (serialHelper.isOpen()) {
Toast.makeText(this, Const.SPORT_NAME + "串口已经打开", Toast.LENGTH_SHORT).show();
return;
}
try {
serialHelper.open();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(this, Const.SPORT_NAME + "串口打开成功", Toast.LENGTH_SHORT).show();
}
//关闭串口方法
public void closeSerialPort() {
if (serialHelper.isOpen()) {
serialHelper.close();
Toast.makeText(this, Const.SPORT_NAME + "串口已经关闭", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.left_reduce:
String stringValue1 = wine_number.getText().toString();
//Log.d("XTQXTQXTQ",stringValue1);
int value1 = Integer.parseInt(stringValue1);
//Log.d("XTQXTQXTQ",value1+"");
if ((value1 > 50)) {
int newValue1 = value1 - 10;
wine_number.setText(newValue1 + "");
}
break;
case R.id.left_add:
String stringValue2 = wine_number.getText().toString();
//Log.d("XTQXTQXTQ",stringValue2);
int value2 = Integer.parseInt(stringValue2);
//Log.d("XTQXTQXTQ",value2+"");
if ((value2 < 150)) {
int newValue2 = value2 + 10;
wine_number.setText(newValue2 + "");
}
break;
case R.id.buy_wine:
Toast.makeText(this,"开始发送串口数据",Toast.LENGTH_LONG).show();
openSerialPort();
if (!serialHelper.isOpen()) {
Toast.makeText(this, Const.SPORT_NAME + "串口没打开 发送失败", Toast.LENGTH_SHORT).show();
return;
}
int sendContentInt = Integer.parseInt(wine_number.getText().toString());
//Log.d("100转十六进制" ,String.format("%02x", 100));
String sendContent = "FB01"+yellowWineType+"00"+String.format("%x", sendContentInt)+"00"+"00"+"FE";
Log.d("100转十六进制内容" ,sendContent);
isHexType = true;
//String sendContent = "FB010200960000FE";
if (isHexType) {
serialHelper.sendHex(sendContent);
} else {
serialHelper.sendTxt(sendContent);
}
case R.id.rb_mk:
yellowWineType = "01";
break;
case R.id.rb_ls:
yellowWineType = "02";
break;
case R.id.rb_lx:
yellowWineType = "03";
break;
case R.id.rb_fzz:
yellowWineType = "04";
break;
default:
break;
}
}
//十六进制校验位checkSum
}
package com.xz.andfasterserialport;
public class Const {
//串口 波特率
public static final String SPORT_NAME = "/dev/ttyS1";
public static final int BAUD_RATE = 9600;
public static final String TXT_TYPE_SEND = "hello";
public static final String HEX_TYPE_SEND = "123ABC";
}
主要思路:
添加依赖
首先,你需要在你的Android项目中添加依赖。确保项目build.gradle
(Module级别)文件中有以下配置:
allprojects {
repositories {
maven { url 'https://jitpack.io' } // 或者使用作者推荐的仓库地址
}
}
dependencies {
implementation 'com.github.xiaozhuang799:AndFasterSerialPort:latest.version'
}
初始化并使用串口
接下来,在你的Activity或Service中,初始化SerialHelper
对象,并配置必要的串口参数:
import com.example.andfasterserialport.SerialHelper;
// 假设已经定义了串口名和波特率
final String SERIAL_PORT_NAME = "/dev/ttyS0"; // 根据实际情况更改
final int BAUD_RATE = 9600; // 设置波特率
SerialHelper serialHelper = new SerialHelper(SERIAL_PORT_NAME, BAUD_RATE) {
@Override
protected void onDataReceived(ComBean paramComBean) {
// 处理接收到的数据
byte[] data = paramComBean.getData();
// 你的逻辑代码
}
};
// 打开串口
serialHelper.open();
// 发送数据示例
byte[] sendBuffer = "Hello, Serial Port!".getBytes();
serialHelper.send(sendBuffer);
// 不要忘记在适当的时候关闭串口
serialHelper.close();
确保在使用串口前,已添加必要的权限到AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 对于Android 6.0及以上,还需要在运行时请求这些权限 -->
参考项目地址:
GitCode - 全球开发者的开源社区,开源代码托管平台