Android--关于串口通信
利用串口,可以让Android主板与各种传感器和智能设备之间通信。Google自己有一个关于Android串口通信。
集成环境
一般串口通信开发,需要用到JNI和NDK方面的知识。首先需要搭建环境,导入相应的.so文件(.so文件是Unix的动态连接库,本身是二进制文件,是由C/C++编译而来的),没有就自己新建libs,将.so文件复制进去。
之后需要再Gradle文件,将libs中的东西引入编译,不然访问不到。如下图
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
......
/*选择处理器相应的架构*/
ndk {
abiFilters "armeabi"
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
.....
/*加载 so库*/
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
Google的demo中的 SerialPort.java (串口代码)
/*
* Copyright 2009 Cedric Priscal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android_serialport_api;
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;
import android.util.Log;
public class SerialPort {
private static final String TAG = "SerialPort";
/*
* Do not remove or rename the field mFd: it is used by native method close();
*/
private FileDescriptor mFd;
private FileInputStream mFileInputStream;//输入流
private FileOutputStream mFileOutputStream;//输出流
// 设备号(串口地址),波特率,flags 默认为0
public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
/* Check access permission 检查权限 */
if (!device.canRead() || !device.canWrite()) {
try {
/* Missing read/write permission, trying to chmod the file */
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 666 " + 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
private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();
static {
System.loadLibrary("serial_port");
}
}
// 如何使用
SerialPort serial = new SerialPort(new File("/dev/goc_serial"),115200,0);
**[SerialPortUtil](https://juejin.cn/post/6844903606982967303)**
public class SerialPortUtil {
private SerialPort serialPort = null;
private InputStream inputStream = null;
private OutputStream outputStream = null;
private ReceiveThread mReceiveThread = null;
private boolean isStart = false;
/**
* 打开串口,接收数据
* 通过串口,接收单片机发送来的数据
*/
public void openSerialPort() {
try {
serialPort = new SerialPort(new File("/dev/ttyS0"), 9600, 0);
//调用对象SerialPort方法,获取串口中"读和写"的数据流
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
isStart = true;
} catch (IOException e) {
e.printStackTrace();
}
getSerialPort();
}
/**
* 关闭串口
* 关闭串口中的输入输出流
*/
public void closeSerialPort() {
Log.i("test", "关闭串口");
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
isStart = false;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送数据
* 通过串口,发送数据到单片机
*
* @param data 要发送的数据
*/
public void sendSerialPort(String data) {
try {
byte[] sendData = DataUtils.HexToByteArr(data);//字符串转为字节数组
outputStream.write(sendData);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void getSerialPort() {
if (mReceiveThread == null) {
mReceiveThread = new ReceiveThread();
}
mReceiveThread.start();
}
/**
* 接收串口数据的线程
*/
private class ReceiveThread extends Thread {
@Override
public void run() {
super.run();
while (isStart) {
if (inputStream == null) {
return;
}
byte[] readData = new byte[1024];
try {
int size = inputStream.read(readData);
if (size > 0) {
String readString = DataUtils.ByteArrToHex(readData, 0, size);//字节数组转为字符串
//EventBus.getDefault().post(readString);事件通知,拿到数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 串口数据转换工具类
*/
public class DataUtils {
//-------------------------------------------------------
// 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数
public static int isOdd(int num) {
return num & 1;
}
//-------------------------------------------------------
//Hex字符串转int
public static int HexToInt(String inHex) {
return Integer.parseInt(inHex, 16);
}
public static String IntToHex(int intHex){
return Integer.toHexString(intHex);
}
//-------------------------------------------------------
//Hex字符串转byte
public static byte HexToByte(String inHex) {
return (byte) Integer.parseInt(inHex, 16);
}
//-------------------------------------------------------
//1字节转2个Hex字符
public static String Byte2Hex(Byte inByte) {
return String.format("%02x", new Object[]{inByte}).toUpperCase();
}
//-------------------------------------------------------
//字节数组转转hex字符串
public static String ByteArrToHex(byte[] inBytArr) {
StringBuilder strBuilder = new StringBuilder();
for (byte valueOf : inBytArr) {
strBuilder.append(Byte2Hex(Byte.valueOf(valueOf)));
strBuilder.append(" ");
}
return strBuilder.toString();
}
//-------------------------------------------------------
//字节数组转转hex字符串,可选长度
public static String ByteArrToHex(byte[] inBytArr, int offset, int byteCount) {
StringBuilder strBuilder = new StringBuilder();
int j = byteCount;
for (int i = offset; i < j; i++) {
strBuilder.append(Byte2Hex(Byte.valueOf(inBytArr[i])));
}
return strBuilder.toString();
}
//-------------------------------------------------------
//转hex字符串转字节数组
public static byte[] HexToByteArr(String inHex) {
byte[] result;
int hexlen = inHex.length();
if (isOdd(hexlen) == 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;
}
/**
* 按照指定长度切割字符串
*
* @param inputString 需要切割的源字符串
* @param length 指定的长度
* @return
*/
public static List<String> getDivLines(String inputString, int length) {
List<String> divList = new ArrayList<>();
int remainder = (inputString.length()) % length;
// 一共要分割成几段
int number = (int) Math.floor((inputString.length()) / length);
for (int index = 0; index < number; index++) {
String childStr = inputString.substring(index * length, (index + 1) * length);
divList.add(childStr);
}
if (remainder > 0) {
String cStr = inputString.substring(number * length, inputString.length());
divList.add(cStr);
}
return divList;
}
/**
* 计算长度,两个字节长度
*
* @param val value
* @return 结果
*/
public static String twoByte(String val) {
if (val.length() > 4) {
val = val.substring(0, 4);
} else {
int l = 4 - val.length();
for (int i = 0; i < l; i++) {
val = "0" + val;
}
}
return val;
}
/**
* 校验和
*
* @param cmd 指令
* @return 结果
*/
public static String sum(String cmd) {
List<String> cmdList = DataUtils.getDivLines(cmd, 2);
int sumInt = 0;
for (String c : cmdList) {
sumInt += DataUtils.HexToInt(c);
}
String sum = DataUtils.IntToHex(sumInt);
sum = DataUtils.twoByte(sum);
cmd += sum;
return cmd.toUpperCase();
}
}
串口工具移植到系统
关于USB
public class MainActivity_Toast extends AppCompatActivity {
public static String TAG = "MainActivity_Toast";
private static final int REQUEST_OVERLAY = 5004;
public static boolean CanShowFloat = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main__toast);
UsbManager usbManager = (UsbManager) getApplicationContext().getSystemService(Context.USB_SERVICE);
Map<String, UsbDevice> usbList = usbManager.getDeviceList();
IntentFilter usbDeviceStateFilter = new IntentFilter();
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, usbDeviceStateFilter);
}
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
Log.e(TAG,"拔出usb了");
Toast.makeText(MainActivity_Toast.this,"拔出usb了",Toast.LENGTH_SHORT).show();
}else if(UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)){
Log.e(TAG,"插入usb了");
UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
toastDialog();
Log.e(TAG,"设备的ProductId值为:"+device.getProductId());
Log.e(TAG,"设备的VendorId值为:"+device.getVendorId());
}
}
}
};
/*<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />*/
private void toastDialog() {
WindowManager wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
RequestOverlayPermission(this);
View view = LayoutInflater.from(this).inflate(R.layout.loading_layout,null);
WindowManager.LayoutParams para = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
para.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
para.type = WindowManager.LayoutParams.TYPE_PHONE;
}
wm.addView(view,para);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
onActivityResult(requestCode,resultCode,data,this);
}
/**
* 动态请求悬浮窗权限
*/
public void RequestOverlayPermission(AppCompatActivity Instatnce) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(Instatnce)) {
//启动Activity让用户授权
/*Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1010);*/
String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
Intent intent = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + Instatnce.getPackageName()));
Instatnce.startActivityForResult(intent, REQUEST_OVERLAY);
} else {
CanShowFloat = true;
}
}
}
/** 浮窗权限请求,Activity执行结果,回调函数 */
public void onActivityResult(int requestCode, int resultCode, Intent data, final AppCompatActivity Instatnce)
{
// Toast.makeText(activity, "onActivityResult设置权限!", Toast.LENGTH_SHORT).show();
if (requestCode == REQUEST_OVERLAY) // 从应用权限设置界面返回
{
if(resultCode == AppCompatActivity.RESULT_OK)
{
CanShowFloat = true; // 设置标识为可显示悬浮窗
}
else
{
CanShowFloat = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(Instatnce)) // 若当前未允许显示悬浮窗,则提示授权
{
AlertDialog.Builder builder = new AlertDialog.Builder(Instatnce);
builder.setCancelable(false);
builder.setTitle("悬浮窗权限未授权");
builder.setMessage("应用需要悬浮窗权限,以展示浮标");
builder.setPositiveButton("去添加 权限", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
RequestOverlayPermission(Instatnce);
}
});
builder.setNegativeButton("拒绝则 退出", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.dismiss();
// 若拒绝了所需的权限请求,则退出应用
Instatnce.finish();
System.exit(0);
}
});
builder.show();
}
}
}
}
}
}
分类:
Android
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库