Funpack4-1_DWM3001CDK_基于UWB的手机距离测算
基于DWM3001CDK评估板实现的通过UWB协议与手机距离测算,并将其通过带屏十二指神探(RP2040)展示出来
标签
Funpack活动
十二指神探
UWB
vic
更新2025-04-03
14

1.项目介绍

1.1.目标

本项目是使用DWM3001CDK评估版实现的自定义任务:通过UWB协议测算开发板与手机之间的距离(精确到厘米级别),将距离相关信息通过带屏十二指神探的LCD屏幕展示;同时在手机与开发板距离小于40CM的时候,点亮板载红色LED;在手机与开发板距离大于40CM时,点亮板载的绿色LED。

1.2.效果展示

如下图为实际效果展示,DWM3001C评估版与手机的距离为31CM,此时点亮板载的红色LED,同时在带屏十二指神探的屏幕上面展示实际的距离信息。

如下图,DWM3001C评估版与手机的距离为118CM,此时点亮板载的绿色LED,同时在带屏十二指神探的屏幕上面展示实际的距离信息。

2.硬件连接

在本次项目中,主要使用到两块板卡:DWM3001CDK评估板、电子森林带屏十二指神探

  1. DWM3001CDK评估板
    1. 通过扩展排针接口与十二至神探连接,主要包含:5V、GND、UART_TX、UART_RX
    2. 板载LED:用于距离指示,大于40CM电亮绿灯,否则点亮红灯
    3. DWM3001C模组:与手机进行UWB通信,计算距离信息
  2. 电子森林带屏十二指神探
    1. 通过扩展接口为DWM3001CDK提供5V供电以及UART通信能力
    2. 屏幕:用于接收来自于DWM3001CDK的UWB解析数据,并且按照一定的格式展示出来
    3. USB:连接电脑进行调试以及供电

3.软件说明

3.1.编程软件

本次由于涉及到两款版本,因此对每个板卡使用到的变成软件分别进行介绍

3.1.1.DWM3001CDK

针对于DWM3001CDK评估版,使用的是SEGGER Embedded Studio 8.22a使用的源码工程是官方的QANI-All-FreeRTOS_QNI_3_0_0示例。

需要注意的是,默认情况下由于缺少nRF52833对应的hex生成工具,因此直接编译的时候可能会报错,解决方式是安装nRF-Command-Line-Tools工具。


3.1.2.带屏十二指神探

带屏十二指神探使用的主芯片是RP2040,本次使用的是MicroPython进行编程,因此使用到的工具是Thonny

3.2.软件代码

软件整体分为两个部分:DWM3001CDK上面的LED控制、UWB信息解析以及UART输出;RP2040上面的UART数据接收、解析、LCD屏幕驱动/绘制。

3.2.1.DWM3001CDK

初始化部分

主函数主要流程如下:

  1. 完成外设初始化(时钟、UWB、UART、BLE、LED等)
  2. 启动FreeRTOP任务调度
int main(void) {
// Initialize modules.
clock_init();
reporter_instance.init();

hal_uwb.mcu_sleep_config();

#if NRF_LOG_ENABLED
init_logger_thread();
#endif

// Accessory Nearby Interaction Initialization
niq_init(ResumeUwbTasks, StopUwbTask, (const void *)nrf_crypto_init,
(const void *)nrf_crypto_uninit,
(const void *)nrf_crypto_rng_vector_generate);

// Accessory instructed to act as a Responder or Initiator
niq_set_ranging_role(ACCESSORY_RANGING_ROLE);

// Create Default task: it responsible for WDT and starting of "applications"
AppConfigInit(); /**< load the RAM Configuration parameters from NVM block */

// Start BLE
char advertising_name[32];

snprintf(advertising_name, sizeof(advertising_name), "%s (%08X)", (char*)BoardName, (unsigned int)NRF_FICR->DEVICEADDR[0]);
ble_init(advertising_name);

EventManagerInit();
BoardInit();
if (uwb_init() != DWT_SUCCESS) {
APP_ERROR_HANDLER(NRF_ERROR_RESOURCES);
}
DefaultTaskInit();

// Driver version is available after probing of the DW chip
const char ver[] = FULL_VERSION;
const char *drv_ver = dwt_version_string();
const char *mac_ver = uwbmac_get_version();

char str[256];
int sz;

sz = sprintf(str, "Application: %s\r\n", ApplicationName);
sz += sprintf(&str[sz], "BOARD: %s\r\n", BoardName);
sz += sprintf(&str[sz], "OS: %s\r\n", OsName);
sz += sprintf(&str[sz], "Version: %s\r\n", ver);
sz += sprintf(&str[sz], "%s\r\n", drv_ver);
sz += sprintf(&str[sz], "MAC: %s\r\n", mac_ver);
sz += sprintf(&str[sz], "ACCESSORY_RANGING_ROLE: %s\r\n", (ACCESSORY_RANGING_ROLE) ? "Initiator" : "Responder");
reporter_instance.print(str, sz);

// Start FreeRTOS scheduler.
osKernelStart();

for (;;) {
APP_ERROR_HANDLER(NRF_ERROR_FORBIDDEN);
}
}


BSP初始化

针对于板载相关硬件的初始化代码如下,在主函数中被调用,主要完成:

  1. 板载LED以及按键初始化
  2. UART初始化(对外通信)
void BoardInit(void)
{
bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS);
peripherals_init();

for (int i = 0; i < 6; i++)
{
bsp_board_led_invert(BSP_BOARD_LED_0);
bsp_board_led_invert(BSP_BOARD_LED_1);
bsp_board_led_invert(BSP_BOARD_LED_2);
bsp_board_led_invert(BSP_BOARD_LED_3);
nrf_delay_ms(250);
}

// 串口初始化
uint32_t err_code;
const app_uart_comm_params_t comm_params =
{
UART_0_RX_PIN,
UART_0_TX_PIN,
RTS_PIN_NUMBER,
CTS_PIN_NUMBER,
APP_UART_FLOW_CONTROL_DISABLED,
false,
UART_BAUDRATE_BAUDRATE_Baud115200
};

APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_error_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);

APP_ERROR_CHECK(err_code);
}


UWB处理

在通过SPI接收到DW3110芯片的UWB数据之后,在如下函数中进行解析以及相关处理

  1. 依照获取数据解析出请求头、解析结果,距离信息就在这里进行获取
  2. 依据距离信息进行阈值判断,当距离小于阈值40CM时,点亮红色LED;当距离大于40CM时,点亮绿色LED
  3. 将解析的数据组合成JSON的字符串类型,通过UART发送给十二指神探
static void report_cb(const struct ranging_results *results, void *user_data) {
static bool notify = true;

int len = 0;
int distance_cm;
struct string_measurement *str_result = (struct string_measurement *)user_data;
struct ranging_measurements *rm;
fira_param_t *fira_param = get_fira_config();

if (results->stopped_reason != 0xFF)
return;

session_id = fira_param->session_id;

len = sprintf(str_result->str, "{\"Block\":%" PRIu32 ", \"results\":[",
results->block_index);

for (int i = 0; i < results->n_measurements; i++)
{
if (i > 0)
{
len += snprintf(&str_result->str[len], str_result->len - len, ",");
}

rm = (struct ranging_measurements *)(&results->measurements[i]);

len += snprintf(&str_result->str[len], str_result->len - len,
"{\"Addr\":\"0x%04x\",\"Status\":\"%s\"",
rm->short_addr,
(rm->status) ? ("Err") : ("Ok"));

if (rm->status == 0)
{
distance_cm = (int)rm->distance_mm / 10;

len +=
snprintf(&str_result->str[len], str_result->len - len, ",\"D_cm\":%d",
(int)rm->distance_mm / 10);

if(hal_uwb.is_aoa() == AOA_ENABLED)
{
len += snprintf(&str_result->str[len], str_result->len - len,
",\"LPDoA_deg\":%0.2f,\"LAoA_deg\":%0.2f,\"LFoM\":%d,\"RAoA_deg\":%0.2f",
convert_aoa_2pi_q16_to_deg(rm->local_aoa_measurements[0].pdoa_2pi),
convert_aoa_2pi_q16_to_deg(rm->local_aoa_measurements[0].aoa_2pi),
rm->local_aoa_measurements[0].aoa_fom,
convert_aoa_2pi_q16_to_deg(rm->remote_aoa_azimuth_2pi)
);
}

len += snprintf(&str_result->str[len], str_result->len - len,
",\"CFO_100ppm\":%d", (int)fira_uwb_mcps_get_cfo_ppm());

//uart_send_msg(distance_cm % 255);

// Take action based on distance
if (notify && (distance_cm < MIN_CM_DISTANCE_THRESHOLD))
{
#if LEDS_NUMBER > 1
// Visual indication on the device for "inside the bubble"
bsp_board_led_on(BSP_BOARD_LED_2); // Red LED On
bsp_board_led_off(BSP_BOARD_LED_0); // Green LED Off
#endif

// Send message to trigger notification on the iOS side
send_ios_notification((uint8_t)distance_cm, "You are in the secure bubble.");
notify = false;
}
else if (!notify && (distance_cm > MAX_CM_DISTANCE_THRESHOLD))
{
#if LEDS_NUMBER > 1
// Visual indication on the device for "outside the bubble"
bsp_board_led_off(BSP_BOARD_LED_2); // Red LED Off
bsp_board_led_on(BSP_BOARD_LED_0); // Green LED On
#endif

// Send message to trigger notification on the iOS side
send_ios_notification((uint8_t)distance_cm, "You are out of the secure bubble.");
notify = true;
}

}
len += snprintf(&str_result->str[len], str_result->len - len, "}");
}

len += snprintf(&str_result->str[len], str_result->len - len, "]");

/* Display RSSI, CFO and NLOS */
if (fira_uwb_is_diag_enabled())
{
len = fira_uwb_add_diag(str_result->str, len, str_result->len);
}

len += snprintf(&str_result->str[len], str_result->len - len, "}\r\n");
reporter_instance.print((char*)str_result->str, len);
uart_send_msg(str_result->str, len);
}

3.2.2.RP2040

RP2040的初始化代码如下,主要的逻辑为

  1. 初始化ST7789这个LCD,并且在屏幕上显示默认值
  2. 初始化UART1,设置参数为:115200-8-n-1
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
CENTER_Y = int(disp_width/2)
CENTER_X = int(disp_height/2)
spi_sck=Pin(2)
spi_tx=Pin(3)
spi0=SPI(0,baudrate=4000000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)

display = st7789.ST7789(spi0, disp_width, disp_width,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
xstart=0, ystart=0, rotation=0)
display.fill(st7789.BLACK)

res_block = 0
res_D_cm = 0
res_status = 'Wait'
res_addr = '0x0'
res_cfo = '0'

display.text(font2, "Funpack4-1", 10, 10)
display.text(font2, "DWM3001CDK", 10, 45)
display.text(font2, "Dist: "+str(res_D_cm)+" CM", 10, 90)
display.text(font1, "Block = "+str(res_block), 10, 140)
display.text(font1, "Status = "+str(res_status), 10, 160)
display.text(font1, "Addr = "+str(res_addr) , 10, 180)
display.text(font1, "CFO_100ppm = "+str(res_cfo), 10, 200)
display.text(font1, "-- by vic", 160, 220)

# 初始化 UART,设置 RXGPIO 20TXGPIO 21,波特率为 9600
uart = UART(1, baudrate=115200, bits=8, parity=None, stop=1, tx=Pin(20), rx=Pin(21))


串口数据解析以及处理代码如下,主要动作为

  1. 每次循环从串口获取一个字节的数据,将其使用utf-8格式进行编码,将其加入到字符串缓冲区中
  2. 当检测到\r\n时,表示数据接收完成
  3. 输入的数据是json格式,对其进行解析,将结果刷新到LCD上
received_data = ""
while True:
if uart.any():
# 读取一个字节的数据
byte_data = uart.read(1)
if byte_data:
try:
# 将字节数据转换为字符串
char_data = byte_data.decode('utf-8')
received_data += char_data
# 检查是否接收到 \r\n
if received_data.endswith('\r\n'):
# 去除末尾的 \r\n 并打印
data = received_data.rstrip('\r\n')

# 数据解析
json_data = ujson.loads(received_data)
res_block = json_data['Block']
res_D_cm = json_data['results'][0]['D_cm']
res_status = json_data['results'][0]['Status']
res_addr = json_data['results'][0]['Addr']
res_cfo = json_data['results'][0]['CFO_100ppm']

display.text(font2, "Dist: "+str(res_D_cm)+" CM"+" ", 10, 90)
display.text(font1, "Block = "+str(res_block)+" ", 10, 140)
display.text(font1, "Status = "+str(res_status)+" ", 10, 160)
display.text(font1, "Addr = "+str(res_addr)+" ", 10, 180)
display.text(font1, "CFO_100ppm = "+str(res_cfo)+" ", 10, 200)

# 清空已接收的数据
received_data = ""
except:
print("Received non - UTF-8 byte:", byte_data)
# 清空已接收的数据
received_data = ""


4.小结

通过本次活动,首次使用UWB开发板进行开发测试,该开发板提供的示例程序以及参考文档能够快速的上手UWB相关的测试,但是实际使用中因为单天线的问题,无法直接进行角度测试,这一点比较可惜。

附件下载
dwm.py
运行在RP2040上面的代码
Projects.7z.001
运行在DWM3001CDK评估板上的代码
Projects.7z.002
运行在DWM3001CDK评估板上的代码
团队介绍
一个在deadline上反复横跳,试图不当股东的菜鸡一枚。
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号