在NanoPC-T6开发板上通过USB串口通信实现光源控制功能
- 1、整体任务描述
- [2、 Android Project代码实现](#2、 Android Project代码实现)
-
- [2.1 'com.github.mik3y:usb-serial-for-android:3.8.0'依赖库导入](#2.1 'com.github.mik3y:usb-serial-for-android:3.8.0'依赖库导入)
- [2.2 Java辅助类UsbLightManager实现](#2.2 Java辅助类UsbLightManager实现)
- [2.3 布局文件activity_main.xml代码实现](#2.3 布局文件activity_main.xml代码实现)
- [2.4 调用辅助类和布局MainActivity.java代码实现](#2.4 调用辅助类和布局MainActivity.java代码实现)
- 3、最终实现的灯光控制效果
- 写在最后
1、整体任务描述
最近老师安排了项目上的一项任务:在NanoPC-T6开发板上通过USB串口通信实现光源控制功能。硬件层面的接线和相应的串口指令已经由其他人完成,我只需要负责在Android项目中开发相应的光源控制逻辑即可。

任务可拆分为以下关键内容:
- 1、在Android中识别特定USB设备并连接;
- 2、给相应的串口地址发送特定的灯光控制指令;
灯光控制指令的示意图如下:

USB接口信息如下,无论是Mac还是Windoes下都有唯一的ID,这块只是临时把控制光源的辅助板子USB接线插到电脑上找到唯一的硬件ID用于在Android项目中连接,找到后还需要将该USB线插回板子上。
1️⃣ mac系统查看硬件ID:

2️⃣ windoes系统下查看硬件VID和PID:

2、 Android Project代码实现
2.1 'com.github.mik3y:usb-serial-for-android:3.8.0'依赖库导入
在Android项目的app/build.gradle文件的dependencies项中加入'com.github.mik3y:usb-serial-for-android:3.8.0'。
java
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
// new add
implementation 'com.github.mik3y:usb-serial-for-android:3.8.0'
}
2.2 Java辅助类UsbLightManager实现
对应的路径为:

完整代码如下:
java
package utils;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.util.Log;
// 导入驱动库
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executors;
public class UsbLightManager {
private static final String TAG = "UsbLightManager";
private UsbSerialPort serialPort;
private SerialInputOutputManager usbIoManager;
private final int BAUD_RATE = 115200;
// 用于标记连接状态
private boolean isConnected = false;
public interface LightCallback {
void onDataReceived(String message);
void onError(String message);
void onConnected();
}
private LightCallback callback;
// 串口数据监听器
private final SerialInputOutputManager.Listener mListener = new SerialInputOutputManager.Listener() {
@Override
public void onNewData(byte[] data) {
if (callback != null) callback.onDataReceived(bytesToHex(data));
}
@Override
public void onRunError(Exception e) {
isConnected = false;
if (callback != null) callback.onError("IO 异常: " + e.getMessage());
disconnect();
}
};
public UsbLightManager(Context context, LightCallback callback) {
this.callback = callback;
}
/**
* 获取当前连接状态
*/
public boolean isConnected() {
return isConnected;
}
/**
* 连接设备
*/
public boolean connect(UsbDevice device, UsbDeviceConnection connection, UsbManager usbManager) {
if (device == null || connection == null || usbManager == null) return false;
// 1. 配置设备驱动探测表
ProbeTable customTable = new ProbeTable();
// 添加 GD32 设备 ID
customTable.addProduct(0x28e9, 0x018a, CdcAcmSerialDriver.class);
// 如果需要支持 CH340,可以取消下面注释
// customTable.addProduct(0x1A86, 0xE5E3, Ch34xSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(usbManager);
// 兜底策略:如果自定义表没找到,尝试默认表
if (drivers.isEmpty()) {
drivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
}
UsbSerialDriver driver = null;
for (UsbSerialDriver d : drivers) {
if (d.getDevice().equals(device)) {
driver = d;
break;
}
}
if (driver == null || driver.getPorts().isEmpty()) {
if (callback != null) callback.onError("未找到匹配驱动: " + String.format("0x%04X", device.getVendorId()));
return false;
}
serialPort = driver.getPorts().get(0);
try {
serialPort.open(connection);
// 设置波特率 115200, 8数据位, 1停止位, 无校验
serialPort.setParameters(BAUD_RATE, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
serialPort.setDTR(true);
serialPort.setRTS(true);
usbIoManager = new SerialInputOutputManager(serialPort, mListener);
Executors.newSingleThreadExecutor().submit(usbIoManager);
isConnected = true;
Log.d(TAG, "已连接到设备");
if (callback != null) callback.onConnected();
return true;
} catch (IOException e) {
isConnected = false;
if (callback != null) callback.onError("打开失败: " + e.getMessage());
disconnect();
return false;
}
}
/**
* 控制灯光开关
* @param on true=开灯(默认50%), false=关灯
*/
public void setPower(boolean on) {
if (serialPort == null || !isConnected) return;
byte[] command;
if (on) {
// 开灯指令: A5 00 01 03 32 FF FF 26 (默认50%亮度)
command = new byte[] {
(byte) 0xA5, 0x00, 0x01, 0x03, 0x32, (byte) 0xFF, (byte) 0xFF, 0x26
};
} else {
// 关灯指令: A5 00 01 02 00 00 57
command = new byte[] {
(byte) 0xA5, 0x00, 0x01, 0x02, 0x00, 0x00, 0x57
};
}
writeCommand(command);
}
/**
* 调节亮度 (1-100)
*/
public void setBrightness(int level) {
if (serialPort == null || !isConnected) return;
// 范围限制
if (level < 1) level = 1;
if (level > 100) level = 100;
// 校验和计算公式: 0x58 - level
// 例如 level=1 (0x01) -> 0x58 - 0x01 = 0x57
// 例如 level=50 (0x32) -> 0x58 - 0x32 = 0x26
// 例如 level=100 (0x64) -> 0x58 - 0x64 = -12 (0xF4)
byte checksum = (byte) (0x58 - level);
// 亮度指令: A5 00 01 03 [Level] FF FF [Checksum]
byte[] command = new byte[] {
(byte) 0xA5, (byte) 0x00, (byte) 0x01, (byte) 0x03,
(byte) level, (byte) 0xFF, (byte) 0xFF, checksum
};
writeCommand(command);
}
/**
* 统一发送方法
*/
private void writeCommand(byte[] command) {
try {
if (serialPort != null) {
serialPort.write(command, 100); // timeout 100ms
}
} catch (IOException e) {
Log.e(TAG, "发送指令失败", e);
// 发送失败通常意味着连接断开或不稳定
if (callback != null) callback.onError("发送失败");
}
}
/**
* 断开连接
*/
public void disconnect() {
isConnected = false;
if (usbIoManager != null) {
usbIoManager.stop();
usbIoManager = null;
}
if (serialPort != null) {
try {
serialPort.close();
} catch (IOException ignored) {}
serialPort = null;
}
}
/**
* 辅助工具:字节转Hex字符串
*/
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02X ", b));
return sb.toString();
}
}
2.3 布局文件activity_main.xml代码实现
布局文件res/layout/activity_main.xml完整代码实现如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="20dp">
<!-- <TextView-->
<!-- android:id="@+id/sample_text"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="Hello World!" />-->
<Switch
android:id="@+id/switch_light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="灯光开关"
android:layout_marginBottom="16dp" />
<TextView
android:id="@+id/tv_brightness_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="当前亮度: 0%"
android:textSize="18sp" />
<SeekBar
android:id="@+id/seekbar_brightness"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:max="100"
android:progress="0" />
</LinearLayout>
对应效果示意图如下:

2.4 调用辅助类和布局MainActivity.java代码实现
MainActivity.java完整代码如下:
java
package com.example.lightv2;
import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.Toast;
// 项目utils路径导入
import utils.UsbLightManager;
import com.example.lightv2.databinding.ActivityMainBinding;
// 驱动相关导入
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private UsbLightManager usbManager;
private static final String ACTION_USB_PERMISSION = "com.example.lightv2.USB_PERMISSION";
// 限流防止串口发送过快
private long lastSendTime = 0;
// 【关键修复】防止重复请求权限的标志位
private boolean isPermissionPending = false;
// 记录灯光是否开启的状态 (逻辑锁)
private boolean isLightOn = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// 1. 初始化 Manager
initUsbManager();
// 2. 初始化控件逻辑
setupSwitch();
setupSeekBar();
// 初始状态:未连接时禁用开关
binding.switchLight.setEnabled(false);
}
private void initUsbManager() {
usbManager = new UsbLightManager(this, new UsbLightManager.LightCallback() {
@Override
public void onDataReceived(String message) {
Log.d("GD32", "RX: " + message);
}
@Override
public void onError(String message) {
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "错误: " + message, Toast.LENGTH_LONG).show();
binding.tvBrightnessLabel.setText("状态: 出错 - " + message);
// 出错时重置UI状态
resetUIState();
});
}
@Override
public void onConnected() {
runOnUiThread(() -> {
Toast.makeText(MainActivity.this, "✅ 连接成功!", Toast.LENGTH_SHORT).show();
binding.tvBrightnessLabel.setText("状态: 已连接 (等待开启)");
// 连接成功,允许操作开关
binding.switchLight.setEnabled(true);
// 默认状态为关
binding.switchLight.setChecked(false);
});
}
});
}
@Override
protected void onResume() {
super.onResume();
// 注册广播
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(usbReceiver, filter);
// 【关键修复】如果正在请求权限,或者已经连接了,就不要再反复执行诊断流程了
if (!isPermissionPending && !usbManager.isConnected()) {
diagnoseAndConnect();
}
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(usbReceiver);
// 页面不可见时断开连接,并重置UI
usbManager.disconnect();
resetUIState();
}
private void resetUIState() {
isLightOn = false;
isPermissionPending = false;
binding.switchLight.setChecked(false);
binding.switchLight.setEnabled(false);
binding.seekbarBrightness.setProgress(0);
}
/**
* 【诊断与连接核心】
*/
private void diagnoseAndConnect() {
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
// 步骤 1: 检查物理设备
HashMap<String, UsbDevice> rawDeviceList = manager.getDeviceList();
if (rawDeviceList.isEmpty()) {
binding.tvBrightnessLabel.setText("状态: 未检测到USB设备");
return;
}
// 步骤 2: 查找驱动
ProbeTable customTable = new ProbeTable();
// 你的 GD32 ID
customTable.addProduct(0x28e9, 0x018a, CdcAcmSerialDriver.class);
UsbSerialProber prober = new UsbSerialProber(customTable);
List<UsbSerialDriver> drivers = prober.findAllDrivers(manager);
// 兜底默认驱动
if (drivers.isEmpty()) {
drivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
}
if (drivers.isEmpty()) {
binding.tvBrightnessLabel.setText("状态: 驱动不匹配");
return;
}
UsbSerialDriver driver = drivers.get(0);
UsbDevice device = driver.getDevice();
// 步骤 3: 检查权限
if (manager.hasPermission(device)) {
Log.d("USB_DIAG", "已有权限,直接连接");
connectDevice(device);
} else {
// 【关键修复】只有在没有挂起的请求时才请求权限
if (!isPermissionPending) {
Log.d("USB_DIAG", "请求USB权限...");
isPermissionPending = true; // 标记正在请求
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0;
PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), flags);
manager.requestPermission(device, permissionIntent);
}
}
}
private void connectDevice(UsbDevice device) {
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbDeviceConnection connection = manager.openDevice(device);
usbManager.connect(device, connection, manager);
}
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
// 【关键修复】收到结果,无论成功失败,都重置标志位
isPermissionPending = false;
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
connectDevice(device);
}
} else {
Toast.makeText(MainActivity.this, "❌ 权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}
}
};
/**
* 设置开关逻辑
*/
private void setupSwitch() {
binding.switchLight.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// 如果是系统自动重置状态(如断开连接时),不发送指令
if (!usbManager.isConnected() && buttonView.isEnabled()) {
return;
}
if (!usbManager.isConnected()) {
// 防止未连接时用户强行点击(虽然已禁用,加一层保险)
buttonView.setChecked(false);
return;
}
usbManager.setPower(isChecked);
isLightOn = isChecked;
if (isChecked) {
binding.tvBrightnessLabel.setText("状态: 灯光开启 (50%)");
// 协议规定:开灯默认 50% 亮度,同步 UI
binding.seekbarBrightness.setProgress(50);
} else {
binding.tvBrightnessLabel.setText("状态: 灯光已关闭");
// 关灯可以归零进度条,显得更直观
binding.seekbarBrightness.setProgress(0);
}
}
});
}
/**
* 设置进度条逻辑
*/
private void setupSeekBar() {
binding.seekbarBrightness.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
// 【逻辑限制】如果灯没开,禁止调节
if (!isLightOn) {
return;
}
binding.tvBrightnessLabel.setText("当前亮度: " + progress + "%");
// 简单的限流,防止滑动太快串口阻塞
long currentTime = System.currentTimeMillis();
if (currentTime - lastSendTime > 50) {
usbManager.setBrightness(progress);
lastSendTime = currentTime;
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 开始触摸时检查状态
if (!isLightOn) {
Toast.makeText(MainActivity.this, "请先打开灯光开关!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 如果没开灯,松手时把进度条弹回 0
if (!isLightOn) {
seekBar.setProgress(0);
return;
}
// 确保最后一次手指离开时的值发送出去
usbManager.setBrightness(seekBar.getProgress());
}
});
}
}
3、最终实现的灯光控制效果
由于csdn上传图片限制大小为5MB,因此这里画质较糊,但可以看出主要功能已经完成。

下面附几张高清图,1️⃣ 打开光源:

2️⃣ 关闭光源:

3️⃣ 亮度97%:

写在最后
- 由于笔者🖊️精力有限且本文更多的目的是通过📒博客记录学习过程并分享更多知识,因此文中部分描述不太具体,如有不太理解💫的地方可在评论区👀留言。非特殊赶deadline⏰或假期⛱️期间,笔者会经常上线回复💬。如有不便之处,请海涵~
- 另外,创造不易,转载请注明出处💗💗💗~