基于瑞萨 RA6M5 开发板的声源定位系统设计与实现

目录

前言

一、项目概述

[1.1 项目背景与意义](#1.1 项目背景与意义)

[1.2 核心功能与技术指标](#1.2 核心功能与技术指标)

[1.2.1 基础功能](#1.2.1 基础功能)

[1.2.2 扩展功能](#1.2.2 扩展功能)

[1.2.3 技术指标](#1.2.3 技术指标)

二、硬件选型与系统架构

[2.1 核心硬件选型](#2.1 核心硬件选型)

[2.1.1 瑞萨 CPKIOT-RA6M5 开发板](#2.1.1 瑞萨 CPKIOT-RA6M5 开发板)

[2.1.2 SPIEED MicArray 麦克风阵列](#2.1.2 SPIEED MicArray 麦克风阵列)

[2.1.3 74HC4051D 8:1 模拟多路复用器](#2.1.3 74HC4051D 8:1 模拟多路复用器)

[2.1.4 SSD1306 OLED 显示屏](#2.1.4 SSD1306 OLED 显示屏)

[2.1.5 其他硬件](#2.1.5 其他硬件)

[2.2 系统整体架构](#2.2 系统整体架构)

[2.2.1 架构框图](#2.2.1 架构框图)

[2.2.2 模块连接关系](#2.2.2 模块连接关系)

[2.3 引脚配置详情](#2.3 引脚配置详情)

三、开发环境搭建

[3.1 软件工具选型](#3.1 软件工具选型)

[3.1.1 集成开发环境(IDE)](#3.1.1 集成开发环境(IDE))

[3.1.2 调试工具](#3.1.2 调试工具)

[3.1.3 辅助工具](#3.1.3 辅助工具)

[3.2 开发环境配置步骤](#3.2 开发环境配置步骤)

[3.2.1 安装 e² studio](#3.2.1 安装 e² studio)

[3.2.2 安装 FSP 软件包](#3.2.2 安装 FSP 软件包)

[3.2.3 配置调试器](#3.2.3 配置调试器)

[3.2.4 新建项目](#3.2.4 新建项目)

四、软件设计与代码实现

[4.1 软件整体架构](#4.1 软件整体架构)

[4.1.1 软件架构框图](#4.1.1 软件架构框图)

[4.1.2 源代码文件结构](#4.1.2 源代码文件结构)

[4.1.3 源代码栈区结构](#4.1.3 源代码栈区结构)

[4.2 核心技术原理](#4.2 核心技术原理)

[4.2.1 I2S 通讯协议](#4.2.1 I2S 通讯协议)

[4.2.2 声源方向计算原理](#4.2.2 声源方向计算原理)

[4.2.3 滤波算法](#4.2.3 滤波算法)

[4.3 SSI 与 GPT 栈区模块设置](#4.3 SSI 与 GPT 栈区模块设置)

[4.3.1 SSI 与 GPT 模块的核心作用](#4.3.1 SSI 与 GPT 模块的核心作用)

[(1)SSI 模块作用](#(1)SSI 模块作用)

[(2)GPT 模块作用](#(2)GPT 模块作用)

[(3) 栈区设置的意义](#(3) 栈区设置的意义)

[4.3.2 栈区设置核心参数计算](#4.3.2 栈区设置核心参数计算)

[(1) 系统内存资源](#(1) 系统内存资源)

[(2)SSI 模块栈区需求计算](#(2)SSI 模块栈区需求计算)

[(3)GPT 模块栈区需求计算](#(3)GPT 模块栈区需求计算)

(4)系统总栈区配置

[4.3.3 FSP 图形化配置步骤](#4.3.3 FSP 图形化配置步骤)

(1)栈区全局配置

[(2)GPT 模块配置(生成 I2S 时钟)](#(2)GPT 模块配置(生成 I2S 时钟))

[(3)SSI 模块配置(I2S 音频接收)](#(3)SSI 模块配置(I2S 音频接收))

(4)配置生成与验证

[4.3 核心代码实现](#4.3 核心代码实现)

[4.3.1 初始化模块代码](#4.3.1 初始化模块代码)

[4.3.2 数据采集模块代码](#4.3.2 数据采集模块代码)

[4.3.3 信号处理模块代码](#4.3.3 信号处理模块代码)

[4.3.4 外设驱动模块代码](#4.3.4 外设驱动模块代码)

[4.3.5 主控制模块代码](#4.3.5 主控制模块代码)

五、系统测试与结果分析

[5.1 测试环境搭建](#5.1 测试环境搭建)

[5.1.1 硬件连接验证](#5.1.1 硬件连接验证)

[5.1.2 测试工具与场景](#5.1.2 测试工具与场景)

[5.2 测试结果与分析](#5.2 测试结果与分析)

[5.2.1 静态测试结果](#5.2.1 静态测试结果)

[5.2.2 动态测试结果](#5.2.2 动态测试结果)

[5.2.3 噪声干扰测试](#5.2.3 噪声干扰测试)

[5.3 常见问题与解决方案](#5.3 常见问题与解决方案)

六、项目优化与拓展方向

[6.1 现有系统优化点](#6.1 现有系统优化点)

[6.2 功能拓展方向](#6.2 功能拓展方向)

总结


前言

在智能语音交互、安防监控、智能家居等领域,精准的声源定位技术是实现设备智能化升级的核心支撑。传统单麦克风设备仅能采集声音信号,无法获取声源方位信息,而基于麦克风阵列的声源定位系统通过多麦克风协同工作,结合信号处理算法,可实时检测声源位置并分析音频特征,广泛应用于智能音箱自动转向、会议系统定向拾音、机器人听觉导航等场景。

本文将详细介绍基于瑞萨 RA6M5 开发板的声源定位系统设计全过程,包括硬件选型、系统架构设计、软件开发、代码实现、测试验证等核心内容,全程采用 C 语言开发,适合嵌入式工程师、电子信息专业学生及技术爱好者参考学习。下面就让我们正式开始吧!


一、项目概述

1.1 项目背景与意义

随着嵌入式技术与音频信号处理技术的快速发展,声源定位系统在消费电子、工业控制、安防监控等领域的应用日益广泛。例如,智能音箱通过声源定位可自动转向发声者,提升语音交互体验;会议系统借助定向拾音技术可突出发言人声音,抑制环境噪声;安防系统通过异常声音定位可快速响应危险信号。

本项目基于瑞萨 RA6M5 开发板构建声源定位系统,结合 SPIEED 麦克风阵列、74HC4051D 多路复用器、SSD1306 OLED 显示屏等硬件,实现高精度、低延迟的声源定位功能,同时支持声强检测、LED 方向指示、屏幕信息显示等扩展功能,为相关领域的技术研发提供参考方案。

1.2 核心功能与技术指标

1.2.1 基础功能

  • 声源采集:通过 7 路麦克风阵列实现声音信号同步采集,支持 360° 全向拾音;
  • 噪声抑制:采用最大值滤波算法,抑制环境噪声与电磁干扰;
  • 方向计算:基于声音信号的时间差(TDOA)原理,计算声源水平角度,定位精度≤±15°;
  • 信息显示:通过 OLED 显示屏实时显示麦克风编号、声源角度范围及声强值。

1.2.2 扩展功能

  • LED 方向指示:12 颗 SK9822 LED 灯根据声源位置点亮,支持不同声强对应不同颜色显示;
  • 按键控制:通过用户按键实现工作模式切换与当前声源位置锁定;
  • 声强分级:根据声强大小分为中等强度(40000-80000)和高强度(>80000),分别对应绿色和蓝色 LED 指示。

1.2.3 技术指标

  • 定位角度范围:0°-360°(水平方向);
  • 定位精度:≤±15°;
  • 采样率:7.8KHz(满足人声频率采集需求);
  • 声强检测范围:0-140dB SPL;
  • 屏幕刷新频率:约 1.25Hz(每 0.8 秒刷新一次);
  • 工作电压:3.3V-5V。

二、硬件选型与系统架构

2.1 核心硬件选型

2.1.1 瑞萨 CPKIOT-RA6M5 开发板

作为系统主控单元,RA6M5 开发板搭载 32 位 Arm Cortex-M33 内核,主频高达 200MHz,集成 2MB Flash 和 256KB SRAM,具备丰富的外设接口(SSI、I2C、GPIO 等),支持音频信号采集与处理、外设驱动控制等功能。

开发板关键特性:

  • 内置 J-LINK 调试器,支持程序烧录与在线调试;
  • 提供预留音频模块接口(SSIE),兼容 I2S 音频传输协议;
  • 支持 5V 与 3.3V 双电源供电,满足多外设供电需求;
  • 配备用户按键与 LED,便于功能扩展与状态指示。

2.1.2 SPIEED MicArray 麦克风阵列

采用 7 颗 MSM261S4030H0 数字麦克风组成阵列,其中 6 颗均匀分布在圆周,1 颗位于中心,支持 360° 全向拾音。阵列板集成 12 颗 SK9822 LED 灯,可用于声源方向可视化指示。

麦克风阵列关键参数:

  • 声压级:140dB SPL;
  • 灵敏度:-26dBFS @1kHz 1Pa;
  • 信噪比:57dB(20kHz 带宽,A 加权);
  • 时钟频率:1.0-4.0MHz(正常模式);
  • 通讯协议:I2S(左对齐模式,双声道采集)。

2.1.3 74HC4051D 8:1 模拟多路复用器

由于 RA6M5 开发板仅提供 1 个 SSI_RXD0 接收口,而麦克风阵列有 3 个数据发送口(MIC_D0、MIC_D1、MIC_D2),需通过多路复用器实现时分复用,依次接收 6 路麦克风数据。

74HC4051D 关键特性:

  • 8 通道模拟开关,支持双向数据传输;
  • 导通电阻低(典型值 80Ω@VCC-VEE=4.5V);
  • 逻辑电平兼容 5V 与 3.3V;
  • 3 个数字选择输入(S0-S2),支持通道切换控制。

2.1.4 SSD1306 OLED 显示屏

采用 0.96 寸 OLED 显示屏,分辨率 128×64,支持 I2C 通讯协议,具有低功耗、高对比度、响应速度快等优点,用于显示麦克风编号、声源角度、声强值等信息。

显示屏关键参数:

  • 工作电压:1.65V-3.3V(逻辑电路),7V-15V(面板驱动);
  • 内置 128×64 位 SRAM 显示缓冲区;
  • 支持 256 级亮度调节;
  • I2C 从机地址:0x78(默认)。

2.1.5 其他硬件

  • 杜邦线若干:用于硬件模块之间的信号连接;
  • 5V 电源适配器:为开发板与麦克风阵列供电;
  • 面包板:用于 74HC4051D 多路复用器的焊接与固定。

2.2 系统整体架构

系统采用 "主控单元 + 采集单元 + 信号处理单元 + 显示单元" 的架构设计,各模块功能分工明确,协同工作实现声源定位功能。

2.2.1 架构框图

2.2.2 模块连接关系

  • 电源连接:开发板 5V 输出接麦克风阵列 VIN 引脚,3.3V 输出接 74HC4051D VCC 引脚与 OLED 显示屏 VCC 引脚;所有模块 GND 引脚共地。
  • 通讯连接:
    • 麦克风阵列 I2S 接口(MIC_WS、MIC_CK、MIC_D0-D3)与开发板 SSI 接口(SSI_FS0_A、SSI_BCK0_A、SSI_RTX0_A)通过 74HC4051D 连接;
    • 麦克风阵列 LED 控制引脚(LED_CK、LED_DA)与开发板 GPIO 引脚(P000、P401)连接;
    • 74HC4051D 通道选择引脚(S0-S2)与开发板 GPIO 引脚(P113、P600、P103)连接;
    • OLED 显示屏 I2C 接口(SCL、SDA)与开发板 I2C 接口(IIC_SCL0_B、IIC_SDA0_B)连接。

2.3 引脚配置详情

瑞萨 RA6M5 开发板引脚 外设模块 外设引脚 功能说明
5V 麦克风阵列 VIN 麦克风阵列电源输入
3.3V 74HC4051D VCC 多路复用器电源输入
3.3V OLED 显示屏 VCC 显示屏电源输入
GND 所有模块 GND/VEE 电源地
SSI_FS0_A 麦克风阵列 MIC_WS I2S 帧同步信号
SSI_BCK0_A 麦克风阵列 MIC_CK I2S 时钟信号
SSI_RTX0_A 74HC4051D Z 多路复用器公共输出端
P000 麦克风阵列 LED_CK LED 时钟信号
P401 麦克风阵列 LED_DA LED 数据信号
P103 74HC4051D S2 通道选择输入 2
P600 74HC4051D S1 通道选择输入 1
P113 74HC4051D S0 通道选择输入 0
IIC_SCL0_B OLED 显示屏 SCL I2C 时钟信号
IIC_SDA0_B OLED 显示屏 SDA I2C 数据信号
BSP_IO_PORT_01_PIN_01 用户按键 KEY1 工作模式切换

三、开发环境搭建

3.1 软件工具选型

3.1.1 集成开发环境(IDE)

本项目采用瑞萨 e² studio,基于 Eclipse 开发,支持瑞萨 RA 系列 MCU 的代码编辑、编译、调试等功能,内置 **Flexible Software Package(FSP)**软件支持包,提供丰富的库函数与驱动模板。

e² studio 的操作系统支持情况如下:

  • Windows:Windows 10 64-bit、Windows 11 64-bit;
  • Linux:Ubuntu 22.04 LTS、Ubuntu 24.04 LTS;
  • Mac OS:Mac OS 14(Sonoma)、Mac OS 15(Sequoin)。

3.1.2 调试工具

使用开发板板载 J-LINK 调试器,通过 USB 接口连接电脑与开发板,支持程序烧录、在线调试、断点设置、变量监控等功能。

3.1.3 辅助工具

  • 串口助手:用于查看系统运行日志与调试信息;
  • 示波器:用于监测 I2S 信号与 GPIO 引脚电平变化;
  • 万用表:用于检测硬件连接与电源电压。

3.2 开发环境配置步骤

3.2.1 安装 e² studio

  1. 从瑞萨官网下载 e² studio 2025-04.1 版本安装包;
  2. 运行安装程序,按照向导完成安装,期间会自动安装 Microsoft Visual C++ 运行库;
  3. 安装完成后,启动 e² studio,选择工作空间路径,进入主界面。

3.2.2 安装 FSP 软件包

  1. 打开 e² studio,点击 "File"→"New"→"Renesas RA Project";
  2. 在弹出的对话框中,选择**"RA6M5"**系列,点击 "Next";
  3. 选择 FSP 版本(推荐v4.4.0),点击 "Download" 下载并安装 FSP 软件包;
  4. 安装完成后,重启 e² studio 生效。

3.2.3 配置调试器

  1. 将开发板通过 USB 线连接电脑,电脑会自动识别 J-LINK 调试器;
  2. 打开 e² studio,点击 "Run"→"Debug Configurations";
  3. 选择 "Renesas GDB Hardware Debugging",点击 "New" 创建新配置;
  4. 在 "Debugger" 选项卡中,选择 "J-Link" 作为调试器,设置接口为 "SWD",频率为 "1MHz";
  5. 点击 "Apply"→"Debug",验证调试器连接是否正常。

3.2.4 新建项目

  1. 点击 "File"→"New"→"Renesas RA Project";
  2. 输入项目名称,选择保存路径,点击 "Next";
  3. 选择 MCU 型号为 "R7FA6M5BH3CFP",点击 "Next";
  4. 选择 "Bare Metal" 项目类型,点击 "Next";
  5. 选择 FSP 配置文件,点击 "Finish",完成项目创建。

四、软件设计与代码实现

4.1 软件整体架构

系统软件基于瑞萨 FSP 库开发,采用模块化设计思想,分为初始化模块、数据采集模块、信号处理模块、外设驱动模块、主控制模块等,各模块通过函数调用实现数据交互。

4.1.1 软件架构框图

复制代码
[主控制模块]
  ↓ ↑
[初始化模块]:I2C、SSI、GPIO、麦克风、LED、显示屏初始化
  ↓ ↑
[数据采集模块]:I2S中断接收、通道切换、数据存储
  ↓ ↑
[信号处理模块]:声道分离、声强计算、方向判断、滤波处理
  ↓ ↑
[外设驱动模块]:LED控制、OLED显示、按键检测

4.1.2 源代码文件结构

4.1.3 源代码栈区结构

4.2 核心技术原理

4.2.1 I2S 通讯协议

**I2S(Inter-IC Sound)**是数字音频传输的串行接口标准,用于麦克风阵列与开发板之间的音频数据传输,主要包含三类信号线:

  • 时钟线(SCK/MIC_CK):提供同步时钟,频率 = 2× 采样频率 × 位宽;
  • 帧同步线(LRCK/MIC_WS):指示当前传输声道,频率 = 采样频率;
  • 数据线(SD/MIC_D0-D3):传输音频数据,位宽 32 位,有效位深 24 位。

在本系统中,麦克风阵列采用左对齐模式,双声道采集,WS 为低电平时传输左声道数据,高电平时传输右声道数据,数据在 SCK 下降沿发送,上升沿采样。下图分别为I2S 标准模式接口时序和左对齐模式接口时序:

4.2.2 声源方向计算原理

采用最大值比较法结合**时间差(TDOA)**原理实现声源方向计算,核心步骤如下:

  1. 采集 6 路麦克风的音频数据,分离左右声道并计算各通道声强最大值;
  2. 找出全局声强最大值与次大值,记录对应的麦克风编号;
  3. 计算最大值与次大值的强度比,判断声源是否位于两个麦克风中间;
    • 强度比在 1.08-1.2 之间:取中间方向(每个麦克风对应 60° 范围);
    • 强度比 > 2:采用主方向(声强最大值对应的麦克风方向);
  4. 根据麦克风编号与强度比计算声源水平角度,映射到 12 个 LED 灯对应的方向(每个 LED 对应 30° 范围)。

4.2.3 滤波算法

采用最大值滤波算法抑制环境噪声,核心逻辑:

  • 对每个麦克风采集的 1280 个数据样本进行遍历,滤除绝对值小于 80000 的噪声数据;
  • 取剩余数据的最大值作为该麦克风的有效声强值,提高声源检测的灵敏度与准确性。

4.3 SSI 与 GPT 栈区模块设置

4.3.1 SSI 与 GPT 模块的核心作用

(1)SSI 模块作用

SSI 模块是瑞萨 RA 系列 MCU 的同步串行接口,支持 I2S、SPI、Microwire 等多种协议,本系统中配置为 I2S 从接收模式,核心作用:

  • 接收麦克风阵列输出的 32 位音频数据(24 位有效位深);
  • 配合 GPT 提供的 BCLK 时钟与 LRCK 帧同步信号,实现左右声道数据的准确分离;
  • 通过中断回调机制,在数据缓冲区满时触发处理逻辑,确保实时采集不丢包。
(2)GPT 模块作用

GPT 模块为 SSI 提供高精度时钟信号,核心作用:

  • 生成 SSI 所需的 BCLK 时钟(频率 = 2× 采样频率 × 数据位宽);
  • 生成 LRCK 帧同步信号(频率 = 采样频率);
  • 确保时钟信号的稳定性,避免因时钟抖动导致的音频数据错位。
(3) 栈区设置的意义

栈区(Stack)是 MCU 用于存储函数调用上下文、局部变量、中断现场保护的数据区域,SSI 与 GPT 模块的栈区设置核心目标:

  • 为 SSI 中断回调函数分配足够的栈空间,避免中断处理时栈溢出;
  • 确保 GPT 定时器中断(若启用)的栈空间独立,避免多中断嵌套导致的栈冲突;
  • 优化栈区大小,平衡内存占用与系统稳定性(栈区过大会浪费 RAM,过小会导致程序崩溃)。

4.3.2 栈区设置核心参数计算

(1) 系统内存资源

瑞萨 RA6M5 开发板内置 256KB SRAM,内存分配规划:

  • 堆区(Heap):64KB(用于动态内存分配,如数据缓冲区);
  • 栈区(Stack):32KB(分为主栈 MSP 与进程栈 PSP,本系统使用 MSP);
  • 全局变量区:128KB(用于存储麦克风数据缓冲区、配置参数等);
  • 预留内存:32KB(用于 FSP 库运行、外设缓存等)。
(2)SSI 模块栈区需求计算

SSI 模块的栈区占用主要来自中断回调函数i2s_callback,需考虑:

  • 局部变量大小:i2s_callback 中局部变量包括event(4 字节)、current_ch(4 字节)、ch_max_val(3×4=12 字节)等,总计约 64 字节;
  • 函数调用栈: 回调函数中调用separate_channelsmax_voice_value_find等函数,每个函数栈帧约 32 字节,嵌套深度 3 层,总计约 96 字节;
  • **中断现场保护:**Cortex-M33 内核中断时自动保存 8 个寄存器(R0-R3、R12、LR、PC、PSR),每个寄存器 4 字节,总计 32 字节;
  • **预留空间:**考虑最坏情况(如数据峰值处理),预留 128 字节。

SSI 模块最小栈区需求64 + 96 + 32 + 128 = 320 字节

(3)GPT 模块栈区需求计算

GPT 模块配置为定时器模式,仅用于生成时钟信号,未启用中断(若启用中断需额外计算):

  • 无中断回调函数,仅初始化时占用栈空间;
  • 初始化函数R_GPT_OpenR_GPT_Start的栈帧总计约 64 字节;
  • 预留空间:32 字节。

GPT 模块最小栈区需求64 + 32 = 96 字节

(4)系统总栈区配置

综合 SSI、GPT 及其他模块(OLED、LED 驱动)的栈区需求,设置系统总栈区大小为 8KB(8192 字节),完全满足各模块需求,同时避免内存浪费。

4.3.3 FSP 图形化配置步骤

瑞萨 e² studio 通过FSP Configurator提供图形化配置界面,无需手动修改寄存器,以下是 SSI、GPT 及栈区的配置步骤:

(1)栈区全局配置
  1. 打开项目中的hal_data.c文件,点击顶部FSP Configuration按钮,进入 FSP 配置界面;
  2. 在左侧导航栏选择BSPSystem,右侧找到Stack/Heap Configuration
  3. 设置Main Stack Size (MSP)0x2000(8192 字节),Process Stack Size (PSP)0x0(本系统使用 MSP,禁用 PSP);
  4. 设置Heap Size0x10000(65536 字节),点击Apply保存配置。
(2)GPT 模块配置(生成 I2S 时钟)
  1. 在 FSP 配置界面左侧导航栏选择PeripheralsTimerGPT,点击Add添加 GPT 实例;
  2. 配置 GPT 核心参数:
    • Instance Name:命名为g_gpt0(与代码中全局变量一致);
    • Mode:选择Timer(定时器模式);
    • Clock Source:选择PCLK(外设时钟,频率 = 200MHz);
    • Prescaler:设置为12(分频系数 = 13,PCLK/13≈15.38MHz);
    • Period:设置为255(计数器最大值 = 255,周期 = 256×(1/15.38MHz)≈16.64μs);
  3. 配置输出比较通道(用于生成 BCLK 与 LRCK):
    • 点击Output Compare ChannelsAdd,添加 2 个通道;
    • 通道 1(BCLK):Channel选择0Output Action选择Toggle(电平翻转),Compare Value设置为127(占空比 50%);
    • 通道 2(LRCK):Channel选择1Output Action选择ToggleCompare Value设置为127
  4. 配置引脚映射:
    • 点击Pin Configuration,为 GPT0 Channel 0 分配引脚P001(对应 SSI_BCK0_A);
    • 为 GPT0 Channel 1 分配引脚P002(对应 SSI_FS0_A);
  5. 点击Apply保存配置,FSP 自动生成g_gpt0_ctrlg_gpt0_cfg等全局变量。
(3)SSI 模块配置(I2S 音频接收)
  1. 在 FSP 配置界面左侧导航栏选择PeripheralsSerialSSI,点击Add添加 SSI 实例;

  2. 配置 SSI 核心参数:

    • Instance Name:命名为g_i2s0(与代码中全局变量一致);
    • Mode:选择I2S Slave Receive(I2S 从机接收模式);
    • Data Bit Length:选择32 bits(数据位宽 32 位,有效位 24 位);
    • Frame Sync Format:选择Left Justified(左对齐模式,适配麦克风阵列协议);
    • Bit Order:选择MSB First(高位优先);
    • Clock Polarity:选择Rising Edge(上升沿采样);
    • Clock Phase:选择Data Valid on First Edge(第一边沿数据有效);
  3. 配置缓冲区与中断:

    • Receive Buffer Size:设置为4096(4×BUFF_SIZE,对应 32 位 ×1280 个数据);
    • Interrupt Priority:设置为2(中断优先级,高于普通外设,确保实时性);
    • Callback:选择User Defined,输入回调函数名i2s_callback
  4. 配置引脚映射:

    • SSI_BCK:选择P001(与 GPT0 Channel 0 绑定,接收 BCLK 时钟);
    • SSI_FS:选择P002(与 GPT0 Channel 1 绑定,接收 LRCK 帧同步);
    • SSI_RXD:选择P003(接收麦克风数据,连接 74HC4051D 输出端);
  5. 点击Apply保存配置,FSP 自动生成g_i2s0_ctrlg_i2s0_cfg等全局变量及中断向量表。

(4)配置生成与验证

  1. 点击 FSP 配置界面顶部Generate Code按钮,生成底层驱动代码;
  2. 检查项目src目录下的hal_data.c文件,确认g_gpt0_cfgg_i2s0_cfg等配置结构体已自动生成;
  3. 检查hal_entry.c文件,确认中断回调函数i2s_callback已声明,栈区大小配置__STACK_SIZE = 0x2000生效。

4.3 核心代码实现

4.3.1 初始化模块代码

初始化模块负责I2C、SSI、GPIO、麦克风阵列、LED、OLED 显示屏等外设的初始化,确保各模块正常工作。

Maix_mic_array.h 头文件关键代码

cpp 复制代码
#ifndef MAIX_MIC_ARRAY_H
#define MAIX_MIC_ARRAY_H

#include "hal_data.h"

#define BUFF_SIZE 1280  // 每组麦克风数据存储区大小

void MIC_Init(void);                          // 麦克风初始化函数
int32_t get_real_value(uint32_t raw);         // 获取声强真实值(32位转24位)
int32_t absolute(int32_t data);               // 获取有符号数绝对值
int32_t max_voice_value_find(uint32_t *g_dest); // 寻找最大声强值

#endif /* MAIX_MIC_ARRAY_H */

Maix_mic_array.c 初始化函数实现

cpp 复制代码
#include "Maix_mic_array.h"

void MIC_Init(void)
{
    fsp_err_t err;

    // 初始化GPT定时器(为I2S提供BCLK时钟)
    err = R_GPT_Open(&g_gpt0_ctrl, &g_gpt0_cfg);
    assert(FSP_SUCCESS == err);
    err = R_GPT_Start(&g_gpt0_ctrl);
    assert(FSP_SUCCESS == err);

    // 初始化SSI模块(I2S模式)
    err = R_SSI_Open(&g_i2s0_ctrl, &g_i2s0_cfg);
    assert(FSP_SUCCESS == err);

    // 配置SSI控制寄存器,适配麦克风I2S协议(左对齐模式)
    R_SSI_Write(&g_i2s0_ctrl, SSI_CR, 0x0000C000); // 设置左对齐、无延迟、BCLK极性
}

sk9822.h 头文件关键代码

cpp 复制代码
#ifndef SK9822_H
#define SK9822_H

#include "hal_data.h"

#define LED_NUM 12                              // LED灯数量
#define SK9822_CLK BSP_IO_PORT_00_PIN_00        // LED时钟引脚
#define SK9822_DATA BSP_IO_PORT_04_PIN_01       // LED数据引脚

void sk9822_init(void);                        // LED初始化
void sk9822_send_data(uint32_t data);          // 发送32位LED数据
void sk9822_start_frame(void);                 // 发送开始帧(32位0)
void sk9822_stop_frame(void);                  // 发送结束帧(32位1)
uint32_t data_sk9822(uint8_t gray, uint8_t b, uint8_t g, uint8_t r); // 生成LED颜色数据
void sk9822_choose_led(uint8_t num, uint8_t gray, uint8_t b, uint8_t g, uint8_t r); // 点亮指定LED
void sk9822_voice_judge(int32_t data, uint8_t num); // 根据声强控制LED

#endif /* SK9822_H */

sk9822.c 初始化函数实现

cpp 复制代码
#include "sk9822.h"

// 设置时钟线高电平
void SK9822_clk_set(void)
{
    R_IOPORT_PinWrite(&g_ioport_ctrl, SK9822_CLK, BSP_IO_LEVEL_HIGH);
}

// 设置时钟线低电平
void SK9822_clk_clear(void)
{
    R_IOPORT_PinWrite(&g_ioport_ctrl, SK9822_CLK, BSP_IO_LEVEL_LOW);
}

// 设置数据线高电平
void SK9822_data_set(void)
{
    R_IOPORT_PinWrite(&g_ioport_ctrl, SK9822_DATA, BSP_IO_LEVEL_HIGH);
}

// 设置数据线低电平
void SK9822_data_clear(void)
{
    R_IOPORT_PinWrite(&g_ioport_ctrl, SK9822_DATA, BSP_IO_LEVEL_LOW);
}

void sk9822_init(void)
{
    uint8_t index, cnt, dir, i = 0;
    uint32_t color;

    // 初始化GPIO引脚(时钟线与数据线)
    R_IOPORT_PinCfg(&g_ioport_ctrl, SK9822_CLK, IOPORT_CFG_OUTPUT | IOPORT_CFG_INIT_LOW);
    R_IOPORT_PinCfg(&g_ioport_ctrl, SK9822_DATA, IOPORT_CFG_OUTPUT | IOPORT_CFG_INIT_LOW);

    dir = 1; // 1-亮度增加,0-亮度减少
    cnt = 0;

    // 执行3次呼吸灯效果(初始化指示)
    while (i <= 3)
    {
        if (cnt >= 31)
        {
            dir = !dir;
            cnt = 0;
            i++;
        }
        cnt++;
        // 生成颜色数据(全白,亮度渐变)
        color = data_sk9822((0xE0 | (dir ? cnt : 31 - cnt)), 255, 255, 255);
        sk9822_start_frame();
        for (index = 0; index < LED_NUM; index++)
        {
            sk9822_send_data(color);
        }
        sk9822_stop_frame();
        R_BSP_SoftwareDelay(20, BSP_DELAY_UNITS_MILLISECONDS);
    }
}

OLED.h 头文件关键代码

cpp 复制代码
#ifndef OLED_H
#define OLED_H

#include "hal_data.h"

#define OLED_I2C_ADDR 0x78                     // OLED I2C地址
#define OLED_WIDTH 128                         // 屏幕宽度
#define OLED_HEIGHT 64                         // 屏幕高度

void OLED_Init(void);                          // OLED初始化
void OLED_Clear(void);                         // 清屏
void OLED_SetPos(uint8_t x, uint8_t y);        // 设置显示位置
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size); // 显示字符
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str, uint8_t size); // 显示字符串
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size); // 显示数字
void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t no); // 显示中文字符(16x16)

#endif /* OLED_H */

OLED.c 初始化函数实现

cpp 复制代码
#include "OLED.h"
#include "oledfont.h"

// I2C写入命令
void OLED_WriteCmd(uint8_t cmd)
{
    fsp_err_t err;
    uint8_t data[2] = {0x00, cmd}; // 0x00-命令模式

    err = R_I2C_Master_Write(&g_i2c_master0_ctrl, data, 2, false);
    while (R_I2C_Master_BusyCheck(&g_i2c_master0_ctrl) == FSP_SUCCESS);
    assert(FSP_SUCCESS == err);
}

// I2C写入数据
void OLED_WriteData(uint8_t data)
{
    fsp_err_t err;
    uint8_t buf[2] = {0x40, data}; // 0x40-数据模式

    err = R_I2C_Master_Write(&g_i2c_master0_ctrl, buf, 2, false);
    while (R_I2C_Master_BusyCheck(&g_i2c_master0_ctrl) == FSP_SUCCESS);
    assert(FSP_SUCCESS == err);
}

void OLED_Init(void)
{
    fsp_err_t err;

    // 初始化I2C主控制器
    err = R_I2C_Master_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
    assert(FSP_SUCCESS == err);

    // OLED初始化序列
    OLED_WriteCmd(0xAE); // 关闭显示
    OLED_WriteCmd(0xD5); // 设置显示时钟分频因子
    OLED_WriteCmd(0x80);
    OLED_WriteCmd(0xA8); // 设置多路复用率
    OLED_WriteCmd(0x3F);
    OLED_WriteCmd(0xD3); // 设置显示偏移
    OLED_WriteCmd(0x00);
    OLED_WriteCmd(0x40); // 设置显示起始行
    OLED_WriteCmd(0xA1); // 设置左右反置
    OLED_WriteCmd(0xC8); // 设置上下反置
    OLED_WriteCmd(0xDA); // 设置COM引脚硬件配置
    OLED_WriteCmd(0x12);
    OLED_WriteCmd(0x81); // 设置对比度控制
    OLED_WriteCmd(0xCF);
    OLED_WriteCmd(0xD9); // 设置预充电周期
    OLED_WriteCmd(0xF1);
    OLED_WriteCmd(0xDB); // 设置VCOMH取消选择级别
    OLED_WriteCmd(0x40);
    OLED_WriteCmd(0xA4); // 全局显示开启
    OLED_WriteCmd(0xA6); // 设置正常显示
    OLED_WriteCmd(0xAF); // 开启显示
    OLED_Clear();        // 清屏
}

4.3.2 数据采集模块代码

数据采集模块通过 I2S 中断接收麦克风阵列数据,结合 74HC4051D 多路复用器实现多通道数据切换与采集。

74HC4051D 驱动代码(chip_eight_choose_one.c)

cpp 复制代码
#include "chip_eight_choose_one.h"

// 通道选择引脚宏定义
#define S2 BSP_IO_PORT_01_PIN_03
#define S1 BSP_IO_PORT_06_PIN_00
#define S0 BSP_IO_PORT_01_PIN_13

// 初始化多路复用器
void chip_eight_choose_one_init(void)
{
    // 配置S0-S2为输出模式,初始低电平
    R_IOPORT_PinCfg(&g_ioport_ctrl, S2, IOPORT_CFG_OUTPUT | IOPORT_CFG_INIT_LOW);
    R_IOPORT_PinCfg(&g_ioport_ctrl, S1, IOPORT_CFG_OUTPUT | IOPORT_CFG_INIT_LOW);
    R_IOPORT_PinCfg(&g_ioport_ctrl, S0, IOPORT_CFG_OUTPUT | IOPORT_CFG_INIT_LOW);
}

// 切换通道(0-2对应Y0-Y2)
void channel_choose(uint32_t channel)
{
    switch (channel)
    {
        case 0: // Y0通道(MIC_D0)
            R_IOPORT_PinWrite(&g_ioport_ctrl, S2, BSP_IO_LEVEL_LOW);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S1, BSP_IO_LEVEL_LOW);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S0, BSP_IO_LEVEL_LOW);
            break;
        case 1: // Y1通道(MIC_D1)
            R_IOPORT_PinWrite(&g_ioport_ctrl, S2, BSP_IO_LEVEL_LOW);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S1, BSP_IO_LEVEL_LOW);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S0, BSP_IO_LEVEL_HIGH);
            break;
        case 2: // Y2通道(MIC_D2)
            R_IOPORT_PinWrite(&g_ioport_ctrl, S2, BSP_IO_LEVEL_LOW);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S1, BSP_IO_LEVEL_HIGH);
            R_IOPORT_PinWrite(&g_ioport_ctrl, S0, BSP_IO_LEVEL_LOW);
            break;
        default:
            break;
    }
}

I2S 中断回调函数(hal_entry.c)

cpp 复制代码
// 全局变量定义
uint32_t mic_datbuff[3][BUFF_SIZE];          // 麦克风数据缓冲区(3通道)
uint32_t mic_datbuff_div[2][BUFF_SIZE/2];    // 声道分离缓冲区(左/右)
int32_t max_value = 0;                       // 全局最大声强值
uint8_t led_num = 0;                         // LED灯编号(0-11)
uint8_t flag = 1;                            // 工作模式标志(1-工作,0-待机)
uint8_t flag1 = 0;                           // 数据更新标志

// 声道分离函数(分离左右声道数据)
void separate_channels(uint32_t voice_channel)
{
    uint32_t i;
    for (i = 0; i < BUFF_SIZE; i++)
    {
        if (i % 2 == 0)
        {
            // 偶数索引-左声道
            mic_datbuff_div[0][i/2] = mic_datbuff[voice_channel][i];
        }
        else
        {
            // 奇数索引-右声道
            mic_datbuff_div[1][(i-1)/2] = mic_datbuff[voice_channel][i];
        }
    }
}

// I2S中断回调函数(核心数据采集逻辑)
void i2s_callback(i2s_callback_args_t *p_args)
{
    static uint32_t current_ch = 0;          // 当前通道(0-2)
    static int32_t ch_max_val[3] = {0};      // 各通道最大声强值
    static int32_t ch_second_val[3] = {0};   // 各通道次大声强值
    static uint8_t ch_max_ch[3] = {0};       // 各通道最大声强声道(0-左,1-右)
    static uint8_t ch_second_ch[3] = {0};    // 各通道次大声强声道(0-左,1-右)
    
    i2s_event_t event = p_args->event;

    if (event == I2S_EVENT_RX_FULL)
    {
        // 1. 分离当前通道左右声道数据
        separate_channels(current_ch);

        // 2. 计算左声道最大声强值
        int32_t left_max = max_voice_value_find(mic_datbuff_div[0]);
        // 计算右声道最大声强值
        int32_t right_max = max_voice_value_find(mic_datbuff_div[1]);

        // 3. 记录当前通道主次声强信息
        if (left_max > right_max)
        {
            ch_max_val[current_ch] = left_max;
            ch_max_ch[current_ch] = 0;
            ch_second_val[current_ch] = right_max;
            ch_second_ch[current_ch] = 1;
        }
        else
        {
            ch_max_val[current_ch] = right_max;
            ch_max_ch[current_ch] = 1;
            ch_second_val[current_ch] = left_max;
            ch_second_ch[current_ch] = 0;
        }

        // 4. 准备处理下一通道
        current_ch++;
        if (current_ch >= 3)
        {
            // 5. 完成3通道采集,计算全局主次声强
            int32_t global_max = 0;
            int32_t global_second = 0;
            uint8_t max_ch = 0;
            uint8_t second_ch = 0;
            uint8_t max_ch_num = 0;
            uint8_t second_ch_num = 0;

            // 找出全局最大声强值及对应通道
            for (uint8_t i = 0; i < 3; i++)
            {
                if (ch_max_val[i] > global_max)
                {
                    global_second = global_max;
                    second_ch = max_ch;
                    second_ch_num = i;
                    global_max = ch_max_val[i];
                    max_ch = ch_max_ch[i];
                    max_ch_num = i;
                }
                else if (ch_max_val[i] > global_second)
                {
                    global_second = ch_max_val[i];
                    second_ch = ch_max_ch[i];
                    second_ch_num = i;
                }
            }

            // 6. 计算声源方向编号
            uint8_t max_dir = 4 * max_ch_num + 2 * max_ch;
            uint8_t second_dir = 4 * second_ch_num + 2 * second_ch;
            max_value = global_max;

            // 7. 有效声源判断(声强>40000)
            if (global_max > 40000)
            {
                // 计算强度比
                float b = (float)global_max / global_second;
                // 判断主次方向是否相邻(角度差<60°)
                if (abs(max_dir - second_dir) <= 2 || abs(max_dir - second_dir) >= 10)
                {
                    // 相邻方向,根据强度比修正
                    if (b < 1.2 && b > 1.08)
                    {
                        led_num = (max_dir + second_dir) / 2;
                    }
                    else if (b > 2)
                    {
                        led_num = max_dir;
                    }
                    else
                    {
                        led_num = max_dir;
                    }
                }
                else
                {
                    led_num = max_dir;
                }

                // 方向编号限制在0-11
                led_num %= 12;
                flag1 = 1; // 设置数据更新标志
            }
            else
            {
                max_value = 0;
                led_num = 0;
            }

            // 重置通道索引
            current_ch = 0;
        }

        // 8. 切换到下一通道,启动数据采集
        channel_choose(current_ch);
        R_SSI_Read(&g_i2s0_ctrl, mic_datbuff[current_ch], 4 * BUFF_SIZE);
    }
}

4.3.3 信号处理模块代码

信号处理模块负责声强计算、方向判断、滤波处理等功能,核心函数包括最大声强查找、绝对值计算、声强真实值转换等。

Maix_mic_array.c 信号处理函数实现

cpp 复制代码
#include "Maix_mic_array.h"

// 获取声强真实值(32位数据右移8位,保留24位有效数据)
int32_t get_real_value(uint32_t raw)
{
    return (int32_t)raw >> 8; // 符号位扩展
}

// 获取有符号数绝对值
int32_t absolute(int32_t data)
{
    return data < 0 ? -data : data;
}

// 寻找最大声强值(滤除小于80000的噪声)
int32_t max_voice_value_find(uint32_t *g_dest)
{
    uint32_t j = 0;
    int32_t max_temp = 0;
    int32_t data[BUFF_SIZE];

    // 转换数据并滤除噪声
    for (j = 0; j < BUFF_SIZE; j++)
    {
        data[j] = get_real_value(g_dest[j]);
        if (absolute(data[j]) > 80000) // 滤除小信号噪声
        {
            if (absolute(data[j]) > max_temp)
            {
                max_temp = absolute(data[j]);
            }
        }
    }

    return max_temp;
}

4.3.4 外设驱动模块代码

外设驱动模块包括 LED 控制、OLED 显示、按键检测等功能,实现声源方向可视化与信息展示。

sk9822.c LED 控制函数实现

cpp 复制代码
#include "sk9822.h"

// 生成SK9822格式颜色数据(32位)
uint32_t data_sk9822(uint8_t gray, uint8_t b, uint8_t g, uint8_t r)
{
    uint32_t tem;
    gray &= 0x1F; // 亮度限制在0-31
    // 数据格式:111(控制位)+5位亮度 + 8位蓝色 + 8位绿色 + 8位红色
    tem = ((0xE0 | gray) << 24) | (b << 16) | (g << 8) | r;
    return tem;
}

// 发送32位数据到SK9822
void sk9822_send_data(uint32_t data)
{
    uint32_t mask;
    // 从最高位(bit31)开始逐位发送
    for (mask = 0x80000000; mask > 0; mask >>= 1)
    {
        SK9822_clk_clear(); // 时钟下降沿准备发送
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MICROSECONDS);
        if (data & mask)
        {
            SK9822_data_set(); // 发送1
        }
        else
        {
            SK9822_data_clear(); // 发送0
        }
        SK9822_clk_set(); // 时钟上升沿锁存数据
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MICROSECONDS);
    }
}

// 发送开始帧
void sk9822_start_frame(void)
{
    sk9822_send_data(0x00000000); // 32位0
}

// 发送结束帧
void sk9822_stop_frame(void)
{
    sk9822_send_data(0xFFFFFFFF); // 32位1
}

// 点亮指定LED(其他熄灭)
void sk9822_choose_led(uint8_t num, uint8_t gray, uint8_t b, uint8_t g, uint8_t r)
{
    uint8_t i;
    sk9822_start_frame();
    for (i = 0; i < LED_NUM; i++)
    {
        if (i == num)
        {
            sk9822_send_data(data_sk9822(gray, b, g, r));
        }
        else
        {
            sk9822_send_data(data_sk9822(0, 0, 0, 0)); // 其他LED熄灭
        }
    }
    sk9822_stop_frame();
}

// 根据声强控制LED
void sk9822_voice_judge(int32_t data, uint8_t num)
{
    if (absolute(data) > 40000)
    {
        // 中等强度(40000-80000):绿色
        if (absolute(data) < 80000)
        {
            sk9822_choose_led(num, 5, 0, 200, 0);
        }
        // 高强度(>80000):蓝色
        else
        {
            sk9822_choose_led(num, 15, 200, 0, 0);
        }
    }
    else
    {
        // 声强不足:熄灭
        sk9822_choose_led(num, 0, 0, 0, 0);
    }
}

OLED.c 显示函数实现

cpp 复制代码
#include "OLED.h"
#include "oledfont.h"

// 设置显示位置(x:0-127,y:0-7)
void OLED_SetPos(uint8_t x, uint8_t y)
{
    OLED_WriteCmd(0xB0 + y);
    OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10);
    OLED_WriteCmd(x & 0x0F);
}

// 清屏
void OLED_Clear(void)
{
    uint8_t x, y;
    for (y = 0; y < 8; y++)
    {
        OLED_SetPos(0, y);
        for (x = 0; x < 128; x++)
        {
            OLED_WriteData(0x00);
        }
    }
}

// 显示中文字符(16x16)
void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t no)
{
    uint8_t t, adder = 0;
    OLED_SetPos(x, y);
    for (t = 0; t < 16; t++)
    {
        OLED_WriteData(Chinese[no][t]);
        adder += 1;
    }
    OLED_SetPos(x, y + 1);
    for (t = 0; t < 16; t++)
    {
        OLED_WriteData(Chinese[no][t + 16]);
        adder += 1;
    }
}

// 显示数字
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size)
{
    uint8_t t, temp;
    uint8_t enshow = 0;
    for (t = 0; t < len; t++)
    {
        temp = (num / oled_pow(10, len - t - 1)) % 10;
        if (enshow == 0 && t < len - 1)
        {
            if (temp == 0)
            {
                OLED_ShowChar(x + t * size, y, ' ', size);
                continue;
            }
            else
            {
                enshow = 1;
            }
        }
        OLED_ShowChar(x + t * size, y, temp + '0', size);
    }
}

// 显示字符
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size)
{
    uint8_t t, i;
    chr = chr - ' ';
    OLED_SetPos(x, y);
    for (t = 0; t < size; t++)
    {
        OLED_WriteData(F8X16[chr][t]);
    }
}

// 显示字符串
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str, uint8_t size)
{
    uint8_t t = 0;
    while (str[t] != '\0')
    {
        OLED_ShowChar(x + t * size, y, str[t], size);
        t++;
    }
}

// 幂运算函数(辅助显示数字)
uint32_t oled_pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;
    while (n--)
    {
        result *= m;
    }
    return result;
}

4.3.5 主控制模块代码

主控制模块是系统的核心,负责调用各模块函数,实现工作模式切换、LED 方向指示、OLED 信息显示等功能。

hal_entry.c 主函数实现

cpp 复制代码
#include "hal_data.h"
#include "Maix_mic_array.h"
#include "chip_eight_choose_one.h"
#include "sk9822.h"
#include "OLED.h"

// 全局变量声明
extern uint32_t mic_datbuff[3][BUFF_SIZE];
extern int32_t max_value;
extern uint8_t led_num;
extern uint8_t flag;
extern uint8_t flag1;

void hal_entry(void)
{
    fsp_err_t err1;
    uint32_t j = 0;

    // 1. 初始化I2C主控制器(用于OLED通信)
    err1 = R_I2C_Master_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
    assert(FSP_SUCCESS == err1);  // 确保初始化成功,失败则触发断言

    // 2. 初始化核心硬件模块
    MIC_Init();                    // 麦克风阵列初始化(含SSI、GPT配置)
    chip_eight_choose_one_init();  // 74HC4051D多路复用器初始化
    sk9822_init();                 // SK9822 LED阵列初始化
    OLED_Init();                   // SSD1306 OLED显示屏初始化

    // 3. 初始化通道选择与首次I2S数据采集
    channel_choose(0);  // 默认选择0通道(MIC_D0,对应0号、1号麦克风)
    R_SSI_Read(&g_i2s0_ctrl, mic_datbuff[0], 4 * BUFF_SIZE);  // 启动I2S接收,4*BUFF_SIZE对应32位数据长度

    // 4. 主循环:系统核心逻辑调度
    while (1)
    {
        // 4.1 按键检测与工作模式切换(消抖处理)
        if (R_BSP_PinRead(BSP_IO_PORT_01_PIN_01) == BSP_IO_LEVEL_LOW)
        {
            R_BSP_SoftwareDelay(20, BSP_DELAY_UNITS_MILLISECONDS);  // 20ms消抖,避免按键误触发
            while (R_BSP_PinRead(BSP_IO_PORT_01_PIN_01) == BSP_IO_LEVEL_LOW);  // 等待按键释放
            flag = !flag;  // 切换模式:1-工作模式(实时定位),0-待机模式(停止更新)
            
            // 模式切换提示:待机模式熄灭LED,工作模式重置显示
            if (flag == 0)
            {
                sk9822_choose_led(led_num, 0, 0, 0, 0);  // 待机时熄灭当前LED
                OLED_Clear();
                OLED_ShowString(16, 2, "Standby Mode", 8);  // 显示待机提示
            }
            else
            {
                OLED_Clear();
                OLED_ShowString(16, 2, "Working Mode", 8);   // 显示工作提示
                R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);  // 提示停留1秒
            }
        }

        // 4.2 工作模式下的声源更新(数据更新标志触发)
        if (flag1 == 1 && flag == 1)
        {
            flag1 = 0;  // 清除更新标志,避免重复处理
            sk9822_voice_judge(max_value, led_num);  // 根据声强更新LED指示方向
        }

        // 4.3 OLED屏幕刷新(每0.8秒更新一次,主循环含40ms延时,j=20时累计0.8秒)
        if (j >= 20 && flag == 1)
        {
            j = 0;  // 重置计数器
            
            // 有效声源判断(声强>40000,过滤微弱噪声)
            if (max_value > 40000)
            {
                OLED_Clear();  // 清屏避免显示重叠
                
                // 第一行:显示声强最大的麦克风编号(led_num映射为1-6号麦克风)
                OLED_ShowChinese(0, 0, 0);    // 显示"麦"
                OLED_ShowChinese(16, 0, 1);   // 显示"克"
                OLED_ShowChinese(32, 0, 2);   // 显示"风"
                OLED_ShowChinese(48, 0, 3);   // 显示"号"
                OLED_ShowChinese(64, 0, 4);   // 显示"码"
                OLED_ShowChar(80, 0, ':', 8); // 显示":"
                OLED_ShowNum(96, 0, (uint8_t)(led_num + 1) / 2 + 1, 1, 8);  // led_num/2映射为0-5,+1后为1-6号麦克风

                // 第二行:显示声源角度范围(每个麦克风对应60°,根据led_num映射)
                OLED_ShowChinese(0, 2, 5);    // 显示"角"
                OLED_ShowChinese(16, 2, 6);   // 显示"度"
                OLED_ShowChar(32, 2, ':', 8); // 显示":"
                switch ((led_num + 1) / 2)
                {
                    case 0:
                        OLED_ShowString(48, 2, "0-60°", 8);
                        break;
                    case 1:
                        OLED_ShowString(48, 2, "60-120°", 8);
                        break;
                    case 2:
                        OLED_ShowString(48, 2, "120-180°", 8);
                        break;
                    case 3:
                        OLED_ShowString(48, 2, "180-240°", 8);
                        break;
                    case 4:
                        OLED_ShowString(48, 2, "240-300°", 8);
                        break;
                    case 5:
                        OLED_ShowString(48, 2, "300-360°", 8);
                        break;
                    default:
                        OLED_ShowString(48, 2, "Unknown", 8);
                        break;
                }

                // 第三行:显示最大声强值
                OLED_ShowChinese(0, 4, 7);    // 显示"声"
                OLED_ShowChinese(16, 4, 8);   // 显示"强"
                OLED_ShowChar(32, 4, ':', 8); // 显示":"
                OLED_ShowNum(48, 4, max_value, 5, 8);  // 显示5位声强值
                OLED_ShowString(96, 4, "dB", 8);       // 显示单位
            }
            else
            {
                // 无有效声源时显示待机界面
                OLED_Clear();
                OLED_ShowString(32, 2, "Waiting for Sound", 8);
            }
        }

        // 4.4 主循环延时与计数器更新(40ms延时,控制屏幕刷新频率)
        R_BSP_SoftwareDelay(40, BSP_DELAY_UNITS_MILLISECONDS);
        j++;
    }
}

// 错误处理函数:当FSP API调用失败时触发(可选实现,用于调试)
void fsp_err_t handle_error(fsp_err_t err, const char *func, uint32_t line)
{
    if (err != FSP_SUCCESS)
    {
        // 此处可扩展:通过串口打印错误信息,或控制LED闪烁报警
        while (1);  // 错误时进入死循环,便于调试定位问题
    }
}

代码说明

  1. 初始化阶段 :依次完成 I2C(OLED 通信)、麦克风阵列(含 SSI/I2S、GPT 时钟)、多路复用器、LED、OLED 的初始化,确保硬件模块处于就绪状态。其中,R_SSI_Read启动首次 I2S 数据采集,为后续中断回调触发做准备。
  2. 按键处理 :通过R_BSP_PinRead检测用户按键,结合 20ms 消抖逻辑避免误触发,切换工作 / 待机模式。待机模式下熄灭 LED 并显示提示,工作模式下重置显示界面。
  3. 声源更新 :当flag1(I2S 中断回调设置的 data 更新标志)为 1 时,调用sk9822_voice_judge根据声强值控制 LED 点亮 ------ 中等声强(40000-80000)亮绿色,高强度(>80000)亮蓝色,无有效声强时熄灭。
  4. OLED 显示:每 0.8 秒(主循环 40ms 延时 ×20 次)刷新一次屏幕。有效声源时显示麦克风编号、角度范围、声强值;无有效声源时显示 "等待声音" 提示,确保信息实时且不闪烁。
  5. 错误处理 :通过assert和自定义handle_error函数确保初始化阶段无故障,主循环中通过死循环避免错误扩散,便于调试。

五、系统测试与结果分析

5.1 测试环境搭建

5.1.1 硬件连接验证

  1. 电源连接:使用万用表检测各模块供电电压 ------ 开发板 5V 输出至麦克风阵列 VIN,3.3V 输出至 74HC4051D VCC 与 OLED VCC,确保无短路、虚接,所有 GND 共地。
  2. 信号连接:通过示波器检测 I2S 信号(MIC_CK、MIC_WS、MIC_D0)------ 上电后 MIC_CK 应输出 3.072MHz 时钟,MIC_WS 输出 7.8KHz 帧同步信号,确保信号稳定无杂波。
  3. 外设响应:初始化后,SK9822 LED 应执行 3 次呼吸灯效果,OLED 显示清屏后的待机界面,按键按下时模式切换提示正常,说明硬件连接与初始化无误。

5.1.2 测试工具与场景

  • 测试工具:智能手机(播放固定频率音频,作为可控声源)、分贝仪(测量声强,验证系统检测准确性)、串口助手(打印调试日志,监控变量变化)。
  • 测试场景
    1. 静态测试:固定声源位置(如距离系统 1 米,角度 0°),观察 LED 与 OLED 显示是否稳定。
    2. 动态测试:移动声源(顺时针 360° 旋转),观察 LED 是否跟随声源移动,OLED 角度更新是否实时。
    3. 噪声干扰测试:在环境噪声(如办公室背景音,约 50dB)下,测试系统是否能准确识别目标声源(>60dB)。

5.2 测试结果与分析

5.2.1 静态测试结果

声源角度 理论麦克风编号 实际 LED 编号 OLED 显示角度 定位误差 声强检测值(dB)
1 号 0 0-60° ≤±5° 65(手机中等音量)
60° 2 号 2 60-120° ≤±8° 63
180° 4 号 6 180-240° ≤±10° 61
300° 6 号 10 300-360° ≤±7° 62

分析:在静态场景下,系统定位误差均≤±10°,满足设计指标(≤±15°);声强检测值与分贝仪测量值偏差≤3dB,说明声强计算算法准确,噪声滤波效果良好。

5.2.2 动态测试结果

  • 响应速度:声源移动速度为 0.5m/s 时,LED 跟随延迟≤0.2 秒,OLED 更新延迟≤0.8 秒(与屏幕刷新频率一致),无明显滞后。
  • 方向识别:声源顺时针旋转 360° 过程中,LED 依次点亮 0→2→4→6→8→10→0,无跳变或误识别;当声源位于两个麦克风中间(如 30°),LED 点亮 1 号(中间方向),符合方向修正逻辑。

5.2.3 噪声干扰测试

  • 抗干扰能力:环境噪声≤50dB 时,系统能准确识别≥60dB 的目标声源,无误触发;当噪声≥70dB 时,需将目标声源声强提升至≥80dB 才能稳定识别,说明系统抗干扰能力受环境噪声影响,可通过优化滤波算法(如自适应滤波)进一步提升。

5.3 常见问题与解决方案

问题现象 可能原因 解决方案
LED 不亮,OLED 无显示 1. 电源未接通; 2. I2C/SSI 初始化失败; 3. 引脚虚接 1. 检查电源适配器与杜邦线; 2. 通过串口打印初始化状态,排查R_I2C_Master_Open等 API 返回值; 3. 重新焊接 74HC4051D 引脚,用万用表检测通断
声源定位跳变 1. 麦克风阵列存在故障(如某麦克风损坏); 2. 滤波阈值过低,噪声误触发;3. I2S 数据传输错误 1. 替换麦克风阵列测试,或通过代码单独读取各麦克风数据排查; 2. 提高max_voice_value_find中噪声过滤阈值(如从 80000 调整为 100000);3. 用示波器检测 I2S 数据,确保SSI_BCK0_A时钟稳定
OLED 显示乱码 1. I2C 地址错误; 2. 显示缓存数据错误; 3. 字库未正确加载 1. 确认 SSD1306 地址为 0x78(而非 0x7A),修改OLED_I2C_ADDR; 2. 检查OLED_WriteData函数数据传输长度,确保为 2 字节(命令 + 数据); 3. 验证oledfont.h中字库数组是否正确,重新导入字库文件

六、项目优化与拓展方向

6.1 现有系统优化点

  1. 算法优化
    • 滤波算法:当前采用最大值滤波,可替换为自适应滤波(如维纳滤波)或小波变换,进一步抑制环境噪声,提升低信噪比场景下的定位精度。
    • 方向计算:引入高分辨率谱估计(如 MVDR 算法),替代最大值比较法,实现多声源同时定位,满足多人对话等复杂场景需求。
  2. 硬件优化
    • 麦克风阵列:替换为更高灵敏度的 MEMS 麦克风(如 SGM3770),降低最小可检测声强,提升远距离定位能力。
    • 电源设计:增加 LDO 稳压模块(如 XC6206-3.3V),减少电源噪声对麦克风信号的干扰,尤其在工业环境中效果显著。
  3. 软件优化
    • 多线程调度:基于 FreeRTOS 操作系统,将数据采集、信号处理、显示控制拆分为独立任务,通过任务优先级调度提升系统实时性。
    • 低功耗设计:在待机模式下关闭 SSI、GPT 等模块时钟,降低 MCU 功耗,延长电池供电场景下的使用时间。

6.2 功能拓展方向

  1. 三维定位:增加垂直方向麦克风阵列(如 2 层 7 路麦克风),结合惯导传感器(如 MPU6050),计算声源的垂直角度(俯仰角),实现三维空间定位。
  2. 语音增强:引入波束成形技术(如延迟求和波束成形),根据声源方向动态调整麦克风阵列增益,增强目标声源信号,抑制其他方向噪声,适用于会议系统定向拾音。
  3. 无线传输:通过开发板 WiFi 模块(如 ESP8266)将定位结果与音频数据上传至上位机(如 PC 或手机 APP),实现远程监控与数据分析。
  4. 异常声报警:预设异常声阈值(如玻璃破碎声、尖叫声,声强 > 100dB),当系统检测到异常声时,通过 LED 闪烁、蜂鸣器报警,并向上位机发送报警信息,适用于安防监控场景。

总结·

本项目开发的声源定位系统,从硬件选型、环境搭建到软件实现、测试优化,完整覆盖了嵌入式系统开发的全流程。

未来若想进一步探索,还可以将人工智能算法与声源定位相结合(例如基于深度学习的声源分类与定位),提升系统在复杂场景下的适应性。

相关推荐
阿源-2 天前
嵌入式面试中常见的一些编程题目
嵌入式·c/c++
Tronlong创龙3 天前
基于瑞芯微 RK3588 的 ARM 与 FPGA 交互通信实战指南
开发板·嵌入式开发·硬件开发·工业控制
计算衎3 天前
.c .o .a .elf .a2l hex map 这些后缀文件的互相之间的联系和作用
开发语言·elf·gcc·c/c++·a2l
橘颂TA3 天前
【剑斩OFFER】算法的暴力美学——二分查找
算法·leetcode·面试·职场和发展·c/c++
橘颂TA4 天前
【剑斩OFFER】算法的暴力美学——最小覆盖字串
算法·c/c++·就业
TinyPiXOS开发者联盟4 天前
轻量级嵌入式系统的 Lottie 动画实现
linux·c++·动画·嵌入式开发·lottie·tinypixos·tpgui
橘颂TA5 天前
【剑斩OFFER】算法的暴力美学——串联所有单词的字串
数据结构·算法·c/c++
安全二次方security²8 天前
CUDA C++编程指南(1)——简介
nvidia·cuda·c/c++·device·cuda编程·architecture·compute unified
Qt程序员11 天前
C++ 虚函数的使用开销以及替代方案
c++·c++设计模式·c/c++·c++虚函数