目录
[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.3.3 FSP 图形化配置步骤](#4.3.3 FSP 图形化配置步骤)
[(2)GPT 模块配置(生成 I2S 时钟)](#(2)GPT 模块配置(生成 I2S 时钟))
[(3)SSI 模块配置(I2S 音频接收)](#(3)SSI 模块配置(I2S 音频接收))
[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
- 从瑞萨官网下载 e² studio 2025-04.1 版本安装包;
- 运行安装程序,按照向导完成安装,期间会自动安装 Microsoft Visual C++ 运行库;
- 安装完成后,启动 e² studio,选择工作空间路径,进入主界面。
3.2.2 安装 FSP 软件包
- 打开 e² studio,点击 "File"→"New"→"Renesas RA Project";
- 在弹出的对话框中,选择**"RA6M5"**系列,点击 "Next";
- 选择 FSP 版本(推荐v4.4.0),点击 "Download" 下载并安装 FSP 软件包;
- 安装完成后,重启 e² studio 生效。
3.2.3 配置调试器
- 将开发板通过 USB 线连接电脑,电脑会自动识别 J-LINK 调试器;
- 打开 e² studio,点击 "Run"→"Debug Configurations";
- 选择 "Renesas GDB Hardware Debugging",点击 "New" 创建新配置;
- 在 "Debugger" 选项卡中,选择 "J-Link" 作为调试器,设置接口为 "SWD",频率为 "1MHz";
- 点击 "Apply"→"Debug",验证调试器连接是否正常。
3.2.4 新建项目
- 点击 "File"→"New"→"Renesas RA Project";
- 输入项目名称,选择保存路径,点击 "Next";
- 选择 MCU 型号为 "R7FA6M5BH3CFP",点击 "Next";
- 选择 "Bare Metal" 项目类型,点击 "Next";
- 选择 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)**原理实现声源方向计算,核心步骤如下:
- 采集 6 路麦克风的音频数据,分离左右声道并计算各通道声强最大值;
- 找出全局声强最大值与次大值,记录对应的麦克风编号;
- 计算最大值与次大值的强度比,判断声源是否位于两个麦克风中间;
- 强度比在 1.08-1.2 之间:取中间方向(每个麦克风对应 60° 范围);
- 强度比 > 2:采用主方向(声强最大值对应的麦克风方向);
- 根据麦克风编号与强度比计算声源水平角度,映射到 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_channels、max_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_Open、R_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)栈区全局配置
- 打开项目中的
hal_data.c文件,点击顶部FSP Configuration按钮,进入 FSP 配置界面;- 在左侧导航栏选择
BSP→System,右侧找到Stack/Heap Configuration;- 设置
Main Stack Size (MSP)为0x2000(8192 字节),Process Stack Size (PSP)为0x0(本系统使用 MSP,禁用 PSP);- 设置
Heap Size为0x10000(65536 字节),点击Apply保存配置。
(2)GPT 模块配置(生成 I2S 时钟)
- 在 FSP 配置界面左侧导航栏选择
Peripherals→Timer→GPT,点击Add添加 GPT 实例;- 配置 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);- 配置输出比较通道(用于生成 BCLK 与 LRCK):
- 点击
Output Compare Channels→Add,添加 2 个通道;- 通道 1(BCLK):
Channel选择0,Output Action选择Toggle(电平翻转),Compare Value设置为127(占空比 50%);- 通道 2(LRCK):
Channel选择1,Output Action选择Toggle,Compare Value设置为127;- 配置引脚映射:
- 点击
Pin Configuration,为 GPT0 Channel 0 分配引脚P001(对应 SSI_BCK0_A);- 为 GPT0 Channel 1 分配引脚
P002(对应 SSI_FS0_A);- 点击
Apply保存配置,FSP 自动生成g_gpt0_ctrl、g_gpt0_cfg等全局变量。
(3)SSI 模块配置(I2S 音频接收)
在 FSP 配置界面左侧导航栏选择
Peripherals→Serial→SSI,点击Add添加 SSI 实例;配置 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(第一边沿数据有效);配置缓冲区与中断:
Receive Buffer Size:设置为4096(4×BUFF_SIZE,对应 32 位 ×1280 个数据);Interrupt Priority:设置为2(中断优先级,高于普通外设,确保实时性);Callback:选择User Defined,输入回调函数名i2s_callback;配置引脚映射:
SSI_BCK:选择P001(与 GPT0 Channel 0 绑定,接收 BCLK 时钟);SSI_FS:选择P002(与 GPT0 Channel 1 绑定,接收 LRCK 帧同步);SSI_RXD:选择P003(接收麦克风数据,连接 74HC4051D 输出端);点击
Apply保存配置,FSP 自动生成g_i2s0_ctrl、g_i2s0_cfg等全局变量及中断向量表。
(4)配置生成与验证
- 点击 FSP 配置界面顶部
Generate Code按钮,生成底层驱动代码; - 检查项目
src目录下的hal_data.c文件,确认g_gpt0_cfg、g_i2s0_cfg等配置结构体已自动生成; - 检查
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); // 错误时进入死循环,便于调试定位问题
}
}
代码说明:
- 初始化阶段 :依次完成 I2C(OLED 通信)、麦克风阵列(含 SSI/I2S、GPT 时钟)、多路复用器、LED、OLED 的初始化,确保硬件模块处于就绪状态。其中,R_SSI_Read启动首次 I2S 数据采集,为后续中断回调触发做准备。
- 按键处理 :通过R_BSP_PinRead检测用户按键,结合 20ms 消抖逻辑避免误触发,切换工作 / 待机模式。待机模式下熄灭 LED 并显示提示,工作模式下重置显示界面。
- 声源更新 :当flag1(I2S 中断回调设置的 data 更新标志)为 1 时,调用sk9822_voice_judge根据声强值控制 LED 点亮 ------ 中等声强(40000-80000)亮绿色,高强度(>80000)亮蓝色,无有效声强时熄灭。
- OLED 显示:每 0.8 秒(主循环 40ms 延时 ×20 次)刷新一次屏幕。有效声源时显示麦克风编号、角度范围、声强值;无有效声源时显示 "等待声音" 提示,确保信息实时且不闪烁。
- 错误处理 :通过assert和自定义handle_error函数确保初始化阶段无故障,主循环中通过死循环避免错误扩散,便于调试。
五、系统测试与结果分析
5.1 测试环境搭建
5.1.1 硬件连接验证
- 电源连接:使用万用表检测各模块供电电压 ------ 开发板 5V 输出至麦克风阵列 VIN,3.3V 输出至 74HC4051D VCC 与 OLED VCC,确保无短路、虚接,所有 GND 共地。
- 信号连接:通过示波器检测 I2S 信号(MIC_CK、MIC_WS、MIC_D0)------ 上电后 MIC_CK 应输出 3.072MHz 时钟,MIC_WS 输出 7.8KHz 帧同步信号,确保信号稳定无杂波。
- 外设响应:初始化后,SK9822 LED 应执行 3 次呼吸灯效果,OLED 显示清屏后的待机界面,按键按下时模式切换提示正常,说明硬件连接与初始化无误。
5.1.2 测试工具与场景
- 测试工具:智能手机(播放固定频率音频,作为可控声源)、分贝仪(测量声强,验证系统检测准确性)、串口助手(打印调试日志,监控变量变化)。
- 测试场景 :
- 静态测试:固定声源位置(如距离系统 1 米,角度 0°),观察 LED 与 OLED 显示是否稳定。
- 动态测试:移动声源(顺时针 360° 旋转),观察 LED 是否跟随声源移动,OLED 角度更新是否实时。
- 噪声干扰测试:在环境噪声(如办公室背景音,约 50dB)下,测试系统是否能准确识别目标声源(>60dB)。
5.2 测试结果与分析
5.2.1 静态测试结果
| 声源角度 | 理论麦克风编号 | 实际 LED 编号 | OLED 显示角度 | 定位误差 | 声强检测值(dB) |
|---|---|---|---|---|---|
| 0° | 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 现有系统优化点
- 算法优化 :
- 滤波算法:当前采用最大值滤波,可替换为自适应滤波(如维纳滤波)或小波变换,进一步抑制环境噪声,提升低信噪比场景下的定位精度。
- 方向计算:引入高分辨率谱估计(如 MVDR 算法),替代最大值比较法,实现多声源同时定位,满足多人对话等复杂场景需求。
- 硬件优化 :
- 麦克风阵列:替换为更高灵敏度的 MEMS 麦克风(如 SGM3770),降低最小可检测声强,提升远距离定位能力。
- 电源设计:增加 LDO 稳压模块(如 XC6206-3.3V),减少电源噪声对麦克风信号的干扰,尤其在工业环境中效果显著。
- 软件优化 :
- 多线程调度:基于 FreeRTOS 操作系统,将数据采集、信号处理、显示控制拆分为独立任务,通过任务优先级调度提升系统实时性。
- 低功耗设计:在待机模式下关闭 SSI、GPT 等模块时钟,降低 MCU 功耗,延长电池供电场景下的使用时间。
6.2 功能拓展方向
- 三维定位:增加垂直方向麦克风阵列(如 2 层 7 路麦克风),结合惯导传感器(如 MPU6050),计算声源的垂直角度(俯仰角),实现三维空间定位。
- 语音增强:引入波束成形技术(如延迟求和波束成形),根据声源方向动态调整麦克风阵列增益,增强目标声源信号,抑制其他方向噪声,适用于会议系统定向拾音。
- 无线传输:通过开发板 WiFi 模块(如 ESP8266)将定位结果与音频数据上传至上位机(如 PC 或手机 APP),实现远程监控与数据分析。
- 异常声报警:预设异常声阈值(如玻璃破碎声、尖叫声,声强 > 100dB),当系统检测到异常声时,通过 LED 闪烁、蜂鸣器报警,并向上位机发送报警信息,适用于安防监控场景。
总结·
本项目开发的声源定位系统,从硬件选型、环境搭建到软件实现、测试优化,完整覆盖了嵌入式系统开发的全流程。
未来若想进一步探索,还可以将人工智能算法与声源定位相结合(例如基于深度学习的声源分类与定位),提升系统在复杂场景下的适应性。


