Funpack4-1: 基于DWM3001CDK开发板的安卓测距应用
该项目使用了DWM3001CDK开发板,实现了安卓测距应用的设计,它的主要功能为:查询设备信息,启动initiator/responder模式进行测距,显示距离信息。
标签
Funpack活动
UWB
Android
距离
clr
更新2025-04-03
8

一. 项目介绍

DWM3001CDKDecawave Module 3001 Carrier Development Kit)是Qorvo公司推出的一款超宽带(UWB)开发套件,主要用于高精度定位测距应用。它基于DWM3001C模块,该模块集成了DW3110 UWB收发器Nordic nRF52833(一个支持BLE的低功耗MCU)。本项目通过利用官方的cli固件与uwb模组进行交互,实现手机通过串口获取设备信息,启动initiatorresponder模式进行测距,并能设置大部分相关参数。

项目目标:

1.       实现安卓手机通过串口与开发板进行交互,发送命令并处理返回数据。

2.       配置FiRa测距的相关参数,能够自定义测距的频率(例如慢速、中速、快速等不同频率)、session id、设备地址等参数并保存。

3.       提取测距中获取的数据并展示。

4.       构建简单的图形化控制界面。

 

. 设计思路

项目总体而言为任务3的修改版,通过usb串口连接一块DWM3001CDK,并通过制作的安卓软件与开发板系统通信,实现测距功能(INITIATOR/ RESPONDER)以及查询设备信息,并在界面上实现数据显示。

由于DWM3001CDK搭载的DWM3001C模块(DW3110 IC)只有一个天线而无法实现PDoA测量角度(理论上至少需要2个天线),目前国产安卓手机具有UWB功能的机型非常少。

如果想要实现类似任务二的功能,只通过TWR距离数据来感知anchor的方向就必须搭配imu等传感器进行惯导,但是这会变得非常复杂,并且难以保证精度,同时伴随着非常大的计算量,对于MCU而言也是个挑战,在进行尝试后我选择了放弃。

我选择了Kotlinjetpack compose来开发这个原生安卓应用,并通过usb-serial-for-android 开源库实现通过usb串口连接虚拟ACM设备,利用官方cli固件(DWM3001CDK-DW3_QM33_SDK_CLI-FreeRTOS.hex)调用RESPF/ INITF/ DECAID APP实现测距以及获取距离数据。尽管程序中设计了initiatormulti参数,但由于我没有更多的uwb开发板,所以没有进一步显示与多个uwb anchor的距离数据。相似的,在与能够实现PDoAuwb模块进行测距时,模块能够输出角度数据。


项目采用 MVVM(Model-View-ViewModel)架构:
Model 层:SerialPortRepository 负责底层串口通信和数据解析,

ViewModel 层:UWBViewModel 管理业务逻辑和 UI 状态,

View 层:基于 Jetpack Compose 构建的 UI 组件,负责界面展示和用户交互。


 

软件流程图:
image.pngimage.pngimage.png

.实现过程

1.配置开发环境

安装Android Studio,开启手机的开发者选项和usb调试功能,由于连接开发板需要占用usb接口,我们需要通过无线调试手机。

小米手机通过Android Studio 难以直接通过无线调试连接手机,需要手动使用adb.exe连接,并确保在同一网络环境,具体参考:https://blog.csdn.net/gogoytgo/article/details/137995574


2.具体代码实现
设备连接管理:

  •  MainActivity 中处理 USB 权限请求和设备选择
  • SerialPortRepository 负责实际的串口连接和数据传输
// 请求USB权限
private fun requestUsbPermission(device: UsbDevice) {
val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
val permissionIntent = PendingIntent.getBroadcast(
this,
USB_PERMISSION_REQUEST_CODE,
Intent(ACTION_USB_PERMISSION).apply {
putExtra(UsbManager.EXTRA_DEVICE, device)
},
PendingIntent.FLAG_IMMUTABLE
)
usbManager.requestPermission(device, permissionIntent)
}

// 连接设备
suspend fun connect(device: UsbDevice): Boolean = withContext(Dispatchers.IO) {
val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
connection = usbManager.openDevice(device) ?: return@withContext false
val driver = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)
.find { it.device == device } ?: return@withContext false

usbPort = driver.ports[0].apply {
open(connection)
setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
}

usbIoManager = SerialInputOutputManager(usbPort, listener).apply { start() }
true
}

数据解析

  • 解析设备信息响应
  • 解析测距会话数据
private fun parseReceivedData(data: String) {
when {
data.contains("Qorvo Device ID") -> {
collectingDeviceInfo = true
deviceInfoBuffer.clear()
deviceInfoBuffer.append(data).append("\n")
}
data.contains("SESSION_INFO_NTF") -> {
collectingSessionInfo = true
sessionLineCount = 1
sessionBuffer.clear()
sessionBuffer.append(data).append("\n")
}
// 其他解析逻辑...
}
}

private fun parseCompleteSessionInfo(completeData: String) {
val sessionHandle = "session_handle=(\\d+)".toRegex().find(cleanedData)?.groupValues?.get(1)?.toIntOrNull()
val distance = "distance\\[cm\\]=(\\d+)".toRegex().find(cleanedData)?.groupValues?.get(1)?.toIntOrNull()

if (sessionHandle != null) {
val sessionInfo = SessionInfo(
sessionHandle = sessionHandle,
distance = distance
)
_sessionData.value = _sessionData.value + sessionInfo
}
}


模式控制

  • Initiator 和 Responder 模式的启动/停止
  • 参数配置和命令构建
fun startInitiatorMode() {
viewModelScope.launch {
_currentMode.value = UwbMode.INITIATOR
_isRunning.value = true
val command = buildString {
append(if (currentInitiatorConfig.chan) "-CHAN=9 " else "-CHAN=5 ")
append("-SLOT=${currentInitiatorConfig.slot} ")
// 其他参数...
}
repository.startInitiatorMode(command.trim())
}
}


UI 实现

  • 设备连接状态显示
  • 模式选择界面
  • 测距结果显示
  • 参数配置对话框
@Composable
fun UWBConsoleScreen(
context: Context,
onRequestUsbPermission: (UsbDevice, (Boolean) -> Unit) -> Unit,
viewModel: UWBViewModel = viewModel()
) {
// 状态收集
val isConnected by viewModel.connectionState.collectAsState()
val isRunning by viewModel.isRunning.collectAsState()

Column(modifier = Modifier.fillMaxSize()) {
TopAppBar(title = { Text("UWB Console") }, actions = {
if (isConnected) {
IconButton(onClick = { showDeviceInfoDialog = true }) {
Icon(Icons.Default.Info, "设备信息")
}
}
ConnectionButton(isConnected) { /* 连接/断开逻辑 */ }
})

if (isConnected) {
if (isRunning) {
DistanceDisplay(viewModel.distance.value, viewModel.currentMode.value)
} else {
ModeSelectionButtons(
onInitiatorClick = { viewModel.startInitiatorMode() },
onResponderClick = { viewModel.startResponderMode() }
)
}
}
}
}


参数配置

  • 提供表单验证的参数配置界面
  • 支持不同模式的特定参数
@Composable
fun ConfigDialog(
showDialog: Boolean,
isInitiator: Boolean,
initiatorConfig: InitiatorConfig,
responderConfig: ResponderConfig,
onDismiss: () -> Unit,
onSave: (Any) -> Unit
) {
// 表单状态管理...

if (showDialog) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(if (isInitiator) "Initiator配置" else "Responder配置") },
text = {
Column {
ConfigSwitch("Channel", chan) { updateChan(it) }
ConfigTextField("Pcode", pcode, { updatePcode(it) }, pcodeValid)
// 其他参数字段...
}
},
confirmButton = {
Button(onClick = { /* 保存逻辑 */ }, enabled = formValid) {
Text("保存")
}
}
)
}
}


四. 实现效果


Screenshot_2025-03-31-21-20-01-032_com.example.uwbconsole-edit.pngScreenshot_2025-03-31-21-26-31-084_com.example.uwbconsole-edit.pngScreenshot_2025-03-31-21-27-57-498_com.example.uwbconsole-edit.png

image.png



五. 总结

UWB Console 项目成功实现了通过 Android 设备控制 UWB 模组进行测距的功能。项目采用清晰的架构设计,完善的参数验证机制和直观的用户界面,为 UWB 开发提供了便利的工具。未来可以进一步扩展的功能包括:测距数据的历史记录和图表展示以及多设备同时测距支持



附件下载
UWBConsole.zip
安卓应用项目文件
UWBConsole-debug.apk
安卓应用安装包
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号