2022年电赛-声源定位跟踪系统(E题)总结

  1. 1. 题目描述
  2. 2. 需求分析
  3. 3. 理论分析
    1. 3.1. 信号时延估计
    2. 3.2. 角度计算
    3. 3.3. 距离计算
  4. 4. 硬件部分
  5. 5. 软件部分
    1. 5.1. 数据处理流程图
    2. 5.2. 信号采样部分
    3. 5.3. 数据处理部分
    4. 5.4. GUI 部分
    5. 5.5. 参数保存部分
  6. 6. 演示视频
  7. 7. 感想
  8. 8. 开源地址
  9. 9. 参考文献

今年电赛我选择的是 E 题—声源定位跟踪系统,本篇文章会对整个题目进行分析,并介绍部分的实现代码细节。

题目描述

image-20220807145606546

需求分析

思维导图

理论分析

信号时延估计

在实际应用中,通常使用互相关 (Cross-correlation, CC) 来计算两个信号间的时延。对于离散信号,互相关的定义如下(此处只考虑实数域):

$$\hat R(m)=\sum_{t=0}^{N-1}{f[t]\cdot g[t+m]}$$

因此互相关又被形象地称为“滑动点积”,当两个信号拥有一致的变化趋势时(重合),取得这个表达式的最大值,或者说当互相关运算取得最大值时,就是两个信号重合的时候,此时对时移 $m$ 进行换算,就可得到我们要找的信号相对时延 $\hat\tau$。

角度计算

对于角度计算,我们采用远场模型,即将声源发出的声波到达麦克风阵列时的波形视为平面波。

由此可得:

$$\hat{\tau}=\frac{d\cos\theta}{c}$$

进而可以推出声源相对于麦克风阵列的角度 $\theta$ 为:

$$\theta=\arccos\frac{c\hat\tau}{d}$$

其中,$c$ 为声速,由于声速受气压、温度等因素影响,为减小测量误差,建议根据实际情况进行测量。

距离计算

image-20220807162639623

A、B、C为采集麦克风,间距均为 $d$,P 为声源,由余弦定理可知:

$$PA^2=PB^2+d^2+2d\cdot PB \cdot \cos \angle b$$

$$PC^2=PB^2+d^2-2d\cdot PB \cdot \cos \angle b$$

设麦克风 A 和 B 接收到的声源信号的时延差为 $\Delta t_{AB}$,麦克风 C 和 B 接收到的声源信号的时延差为 $\Delta t_{CB}$,则 $PA$、$PB$、$PC$ 之间的关系为:

$$PC = PB + v\cdot \Delta t_{CB}$$

$$PA = PB + v \cdot \Delta t_{AB}$$

联立上式可得:

$$PB = \frac{\left | 2d^2-v^2 \cdot (\Delta t_{CB}^2+\Delta t_{AB}^2) \right | }{\left | 2v\cdot (\Delta t_{CB} + \Delta t_{AB}) \right | }$$

硬件部分

MCU 使用来自上海先楫半导体的 HPM6750,它采用双 RISC-V 内核,主频高达 816 MHz,支持可配置的分辨率显示屏,刷新率可达 1366x768 60 FPS,拥有 3 个 12 位模拟数字转换器 ADC,最大采样率可达 5 MHz,十分适合本题使用。

主控板照片

声音采集部分使用由三个 MAX4466 麦克风模块组成的线阵。

image-20220807165755018

软件部分

数据处理流程图

image-20220807170424750

信号采样部分

信号采样部分使用 ADC12 的周期转换模式,对音频信号进行采样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void audio_cap_init()
{
/* 以上代码省略 */

/* 配置周期转换模式 */
adc12_prd_config_t prd_config;
prd_config.prescale = 4;
prd_config.period_count = 125; // 100kHz @ 200MHz

for (int i = 0; i < 3; i++)
{
prd_config.ch = mic_adc_channels[i];
adc12_set_prd_config(MIC_ADC_BASE, &prd_config);
}
}

获取采样数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void audio_get_value(float *data, uint32_t count, uint8_t *channel_seq, uint8_t channel_count)
{
/* 进入临界段 */
vTaskSuspendAll();

uint16_t result;

for (uint32_t i = 0; i < count; i++)
{
for (uint8_t j = 0; j < channel_count; j++)
{
adc12_get_prd_result(MIC_ADC_BASE, channel_seq[j], &result);
*(data + j * count + i) = (float)((result >> 4) & ((1 << 12) - 1));
}
clock_cpu_delay_us(10);
}

/* 退出临界段 */
xTaskResumeAll();
}

数据处理部分

在数据处理的部分,使用了 RISC-V 的 DSP 指令集加速处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void sample_data()
{
/* 获取音频数据 */
audio_get_value(mic_data, DATA_LENGTH, CHANNEL_SEQ, CHANNEL_COUNT);

/* 去除直流分量 */
float32_t dc_component;
for (int i = 0; i < CHANNEL_COUNT; i++)
{
/* 求均值,即直流分量 */
dc_component = hpm_dsp_mean_f32(mic_data[i], DATA_LENGTH);
for (int j = 0; j < DATA_LENGTH; j++)
mic_data[i][j] -= dc_component;
}

/* 数据归一化 */
float32_t max_value, min_value;

for (int i = 0; i < CHANNEL_COUNT; i++)
{
max_value = hpm_dsp_max_f32(mic_data[i], DATA_LENGTH, NULL);
min_value = hpm_dsp_min_f32(mic_data[i], DATA_LENGTH, NULL);
for (int j = 0; j < DATA_LENGTH; j++)
{
mic_data[i][j] = (mic_data[i][j] - min_value) * 2 / (max_value - min_value) - 1;
}
}
}

数据测量部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void measure_timer_cb(lv_timer_t *timer)
{
uint32_t max_value_index[3];

sample_data();

/* 信号互相关 */
hpm_dsp_corr_f32(mic_data[0], DATA_LENGTH, mic_data[1], DATA_LENGTH, mic_data_corr);
/* 找出最大值的数组索引,由此得到信号时延 */
hpm_dsp_max_f32(mic_data_corr, DATA_LENGTH * 2 - 1, max_value_index + 0);
hpm_dsp_corr_f32(mic_data[0], DATA_LENGTH, mic_data[2], DATA_LENGTH, mic_data_corr);
hpm_dsp_max_f32(mic_data_corr, DATA_LENGTH * 2 - 1, max_value_index + 1);
hpm_dsp_corr_f32(mic_data[1], DATA_LENGTH, mic_data[2], DATA_LENGTH, mic_data_corr);
hpm_dsp_max_f32(mic_data_corr, DATA_LENGTH * 2 - 1, max_value_index + 2);

/* 以下代码省略 */
}

GUI 部分

基于嵌入式 GUI 框架 LVGL,使用电阻屏进行交互。设计过程中用到了 LVGL 官方的 SquareLine Studio。从可视化的编辑界面到一键生成代码部署,十分方便,大大减少了我们的调试时间。

image-20220807182929059

参数保存部分

由于测评过程中不允许对 MCU 进行烧录,为了便于调试,提高容错的空间,我们设计了实时参数设置的功能。参数的读取与写入,通过芯片内部 BootROM 提供的 ROM API 完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
bool setting_save(const setting_t *data)
{
hpm_stat_t status = status_success;

if (data->magic_number != SETTING_MAGIC_NUMBER)
{
return false;
}

disable_global_irq(CSR_MSTATUS_MIE_MASK);

status |= rom_xpi_nor_erase_block(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
SETTING_FLASH_ADDRESS);
status |= rom_xpi_nor_program(BOARD_APP_XPI_NOR_XPI_BASE, xpi_xfer_channel_auto, &s_xpi_nor_config,
(uint32_t *)data, SETTING_FLASH_ADDRESS, sizeof(setting_t));

enable_global_irq(CSR_MSTATUS_MIE_MASK);

if (status == status_success)
{
return true;
}
else
{
return false;
}
}

void setting_read(setting_t *data)
{
disable_global_irq(CSR_MSTATUS_MIE_MASK);

rom_xpi_nor_read(BOARD_APP_XPI_NOR_XPI_BASE,
xpi_xfer_channel_auto,
&s_xpi_nor_config,
(uint32_t *)data, SETTING_FLASH_ADDRESS,
sizeof(setting_t));

enable_global_irq(CSR_MSTATUS_MIE_MASK);
}

演示视频

感想

刚拿到题的时候还是很懵的,感觉无从下手,后来听了老师的思路讲解,也没咋听懂,于是就先按照自己的想法去做。ADC 采样、正弦波、FFT、FIR,当我把信号滤出来后,思路就断了,不知道如何求出两个信号的到达时间差,然后就开始大量地查阅文献资料,果然看到了老师讲解时提到的那几个名词:GCC,TDOA。然而此时已经是比赛的第二个晚上,我已经感到十分疲惫(从第一天晚上就开始通宵),趴桌子到天亮后继续去和老师探讨,老师建议我们放弃正弦波,改用 Chirp 信号,该信号的频率随着时间而改变,同时互相关后的波峰也比正弦波更明显,这时我们整个的系统方案才定下来,之后就是不停的公式推导、计算、调试……

互相关结果

到最后,角度测量的效果还是挺不错的,但受到麦克风摆放间距的影响,距离的测量误差较大。而且由于调试时是在走廊测试的,但测评时是在室内测评,受到声音的反射波影响,测量效果远不如调试时的效果,这一点没能提前发现,也算是留给学弟学妹们的一个教训。

这次电赛应该是我本科阶段参加的最后一次比赛了。本科期间大大小小的比赛也参加了不少,但不管怎样,赛前充分准备,赛中大胆假设、仔细验证,赛后认真总结反思,通过比赛发现自身的不足并不断成长,这才是参加比赛的意义,比赛的结果永远不是一张获奖证书那么简单。

最后还是要感谢我的队友们,以及学校的指导老师,比赛取得的成绩离不开你们的帮助,谢谢你们。

image-20220807180727632

开源地址

GitHub: tfx2001/nuedc_2022_e

参考文献

本网站所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。