声音在程序里通常是一串采样数据,在硬件实验里却可以变成一组实时跳动的频谱柱。这个实验的价值不只是让 LCD 屏幕显示动画,而是把"声音采集、频域分析、图像绘制、屏幕刷新"串成一条完整的数据处理链路。对于 Python 和 AI 硬件入门学习来说,这类实验非常适合理解传感器数据如何从真实环境进入程序,再经过算法处理后变成可视化结果。
本实验使用 CanMV K210 开发板读取 I2S 麦克风采集到的音频数据,通过 FFT 将声音信号从时域转换到频域,再把频谱幅度绘制成 48 根动态柱状图显示在 LCD 屏幕上。程序还加入了噪声门限、频谱放大、平滑处理、峰值保持和声音强度状态显示,让画面不只是简单抖动,而是更接近常见音乐频谱显示器的视觉效果。
| 学习目标 | 说明 |
|---|---|
| 理解声音采集链路 | 认识 I2S 麦克风如何把环境声音转换成数字音频数据 |
| 掌握 FFT 基础应用 | 使用 FFT 将音频采样数据转换成频谱幅度数据 |
| 学会 LCD 可视化 | 使用 image 绘制文字、网格线、频谱柱和峰值线,再通过 LCD 显示 |
| 理解频谱显示优化 | 使用噪声门限、幅度放大、平滑处理和峰值保持改善显示效果 |
| 建立音频项目思路 | 为后续声音检测、音频交互、声音分类和 AI 音频识别打基础 |
这类实验连接了硬件采集、信号处理和图形显示三个方向。I2S 麦克风负责把声音送入程序,FFT 负责把采样数据转换成频域特征,LCD 负责把计算结果变成可观察的频谱画面。理解这条链路后,后续扩展声音强度检测、噪声提醒、音乐互动装置和 AI 声音识别都会更加自然。
文章目录
理论基础
声音是一种随时间变化的信号。麦克风采集到声音后,程序拿到的是一段一段音频采样数据,这些数据可以理解成声音在时间轴上的变化记录。直接观察原始采样数据并不直观,因为它只表示某一时刻的幅度变化,很难看出低频、中频和高频成分分别有多少。
FFT 的作用就是把声音从时域转换到频域。时域关注"声音随时间如何变化",频域关注"声音由哪些频率成分组成"。在本实验中,rx.record(SAMPLE_POINTS) 负责采集一段音频数据,FFT.run() 负责进行频谱计算,FFT.amplitude() 负责得到频谱幅度。程序再把这些幅度数据映射成屏幕上的频谱柱,高度越高,表示对应频段的能量越强。
I2S 是常见的数字音频通信方式,通常包含数据线、帧同步线和时钟线。本实验使用物理引脚 34、33、32 连接 I2S 麦克风,分别对应数据输入、帧同步和串行时钟。I2S 麦克风输出的是数字音频数据,不是普通高低电平信号,因此不能像按键或 LED 那样用普通 GPIO 直接读取,而是需要通过 maix.I2S 创建音频输入对象进行采样。
环境声音
说话 / 音乐 / 敲击声
I2S 麦克风
采集数字音频
I2S0 输入
D0 / WS / SCLK
CanMV K210
rx.record 读取采样
FFT 频谱计算
时域转频域
幅度数据处理
噪声门限 / 放大 / 平滑
image 图像绘制
网格 / 状态 / 频谱柱 / 峰值线
LCD 显示
实时频谱画面
VCC / GND
麦克风与屏幕供电
这张流程图展示的是声音从真实环境进入程序,再变成 LCD 可视化画面的完整链路。环境声音先被 I2S 麦克风采集,K210 通过 I2S0 接收采样数据,FFT 把采样数据转换成频谱幅度,绘图函数把幅度数据画成柱状图和峰值线,最后通过 LCD 刷新显示。这个流程不是单纯的"屏幕动画",而是一个完整的音频数据处理过程。
频谱显示中还需要处理画面稳定性问题。环境中即使没有明显声音,也会存在底噪;声音突然变化时,频谱柱也可能剧烈抖动。代码中通过 NOISE_GATE 过滤小幅度噪声,通过 AMP_SCALE 放大有效频谱,通过 SMOOTH_FACTOR 平滑柱子高度,通过 PEAK_DROP_SPEED 控制峰值线下降速度。这些参数共同决定了画面是否清晰、稳定和有动态感。
硬件设施
本实验真正使用到的核心模块是 CanMV K210、I2S 音频输入和 LCD 显示。代码没有使用按键、蜂鸣器、电机、摄像头识别等功能,因此这些模块不作为当前实验内容展开。软件侧主要依赖 maix.I2S、maix.FFT、lcd 和 image,分别承担音频采集、频谱计算、屏幕刷新和图像绘制任务。
接线关系可以先通过下面的图片建立整体印象。当前实验重点关注 I2S 麦克风的 D0、WS、SCLK 三根信号线,以及麦克风模块的供电和 GND。LCD 通常通过开发板屏幕接口连接,代码中只通过 lcd.init() 完成初始化,没有单独展开 LCD 引脚映射。
| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,完成音频采集、FFT 计算和 LCD 显示 |
| I2S 麦克风模块 | 声音输入设备 | 通过 I2S 总线向 K210 输入数字音频数据 |
| LCD 屏幕 | 频谱显示设备 | 用于显示标题、声音状态、辅助线、频谱柱和峰值线 |
| 物理引脚 34 | I2S 数据输入 | 代码中注册为 I2S0_IN_D0,用于接收麦克风音频数据 |
| 物理引脚 33 | I2S 帧同步信号 | 代码中注册为 I2S0_WS,用于音频帧同步 |
| 物理引脚 32 | I2S 时钟信号 | 代码中注册为 I2S0_SCLK,用于提供 I2S 数据传输时钟 |
maix.I2S |
音频采集模块 | 创建 I2S 接收对象,并按照指定采样率读取音频数据 |
maix.FFT |
频谱计算模块 | 将采集到的音频数据转换成频域幅度数据 |
image |
图像绘制模块 | 创建画布并绘制文字、横线、柱状图和峰值线 |
lcd |
屏幕显示模块 | 初始化 LCD 并把绘制完成的图像刷新到屏幕 |
time |
节奏控制模块 | 通过毫秒延时控制主循环刷新频率 |
实验中使用的核心模块如下。I2S 麦克风负责输入声音,LCD 负责输出可视化结果,CanMV K210 负责完成采样、计算和绘图。接线时重点检查麦克风数据线、帧同步线、时钟线、供电和 GND,不要把 I2S 麦克风当作普通模拟麦克风或普通数字 GPIO 模块处理。
接线关系主要来自代码中的 fm.register() 配置。当前程序使用 3 个物理引脚连接 I2S 麦克风,其中数据线、帧同步线和时钟线都被注册到 I2S0 对应功能。LCD 初始化在代码中通过 lcd.init() 完成,通常由开发板板级配置或屏幕接口完成。
| 物理引脚 / 接口 | 功能引脚 | 代码常量 | 对应硬件 | 说明 |
|---|---|---|---|---|
| 34 | I2S0_IN_D0 |
I2S_D0_PIN |
I2S 麦克风数据线 | 用于接收麦克风输出的数字音频数据 |
| 33 | I2S0_WS |
I2S_WS_PIN |
I2S 麦克风 WS 线 | 用于音频帧同步 |
| 32 | I2S0_SCLK |
I2S_SCLK_PIN |
I2S 麦克风时钟线 | 用于 I2S 通信时钟 |
| VCC | 模块供电 | 无单独变量 | I2S 麦克风供电端 | 根据麦克风模块标识连接对应电源 |
| GND | 公共地线 | 无单独变量 | I2S 麦克风 GND | 与 CanMV 共地,保证信号参考一致 |
| 板载 LCD 接口 | LCD 显示接口 | lcd.init() |
LCD 屏幕 | 负责频谱画面输出 |
物理引脚是开发板与外设连接的真实接口,I2S 功能引脚是芯片内部为音频通信准备的专用功能,代码常量则是程序中用于管理这些引脚编号的名称。当前代码把 34、33、32 三个物理引脚分别绑定到 I2S0 的数据输入、帧同步和时钟功能上,I2S 麦克风采集到的声音数据会通过这组引脚进入程序,再交给 FFT 模块计算频谱。
程序运行后的显示效果可以参考下面的图片。正常情况下,LCD 上会显示黑色背景、实验标题、声音强度状态、横向辅助线、48 根动态频谱柱和橙色峰值线。靠近麦克风说话、播放音乐或敲击桌面时,频谱柱会随声音强弱发生明显变化。
| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | LCD 显示标题、状态文字和频谱区域 | 如果无画面,检查 LCD 初始化和屏幕连接 |
| 环境安静 | 频谱柱较低或基本不动,状态显示 Sound: Low |
如果柱子很高,可能是噪声门限太低或麦克风噪声较大 |
| 正常说话 | 频谱柱随声音明显跳动,状态可能变为 Sound: Normal |
如果不动,检查 I2S 麦克风接线和采样配置 |
| 声音较大 | 频谱柱明显升高,状态可能变为 Sound: High |
如果柱子顶满屏幕,降低 AMP_SCALE 或提高 NOISE_GATE |
| 峰值线显示 | 柱子顶部出现橙色峰值线,并缓慢下降 | 如果下降太快或太慢,调整 PEAK_DROP_SPEED |
| 画面刷新 | 频谱持续更新,无明显卡顿 | 如果卡顿,适当增加循环延时或减少显示复杂度 |
软件代码
本实验代码围绕 I2S 音频采集和 LCD 频谱显示展开。程序先设置屏幕尺寸、采样率、FFT 点数、频谱柱数量和显示参数,再注册 I2S 麦克风引脚,初始化 LCD 与 I2S 接收对象。主循环持续采集音频数据,经过 FFT 计算频谱幅度,再绘制背景、声音强度状态、频谱柱和峰值线。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能正常运行基础测试程序 |
| CanMV 固件 | 提供 maix.I2S、maix.FFT、lcd、image 等模块 |
固件环境需要支持 I2S 音频采集和 FFT 计算 |
| LCD 屏幕驱动 | 显示频谱画面 | lcd.init() 能正常初始化屏幕 |
| I2S 麦克风 | 输入音频数据 | 物理引脚 34、33、32 与代码配置一致 |
| 串口终端 | 查看报错信息 | 程序异常时可通过串口定位导入、初始化或运行问题 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----湖南创乐博智能科技有限公司----
# 文件名:42_FFT_LCD_show_optimized.py
# 版本:V2.1
# 说明:CanMV K210 声音频谱识别 FFT 可视化实验优化版
from maix import I2S, FFT
from fpioa_manager import fm
import image
import lcd
import time
# =========================
# 基础参数配置
# =========================
LCD_WIDTH = 320
LCD_HEIGHT = 240
SAMPLE_RATE = 38640 # 音频采样率
SAMPLE_POINTS = 1024 # 每次采样点数
FFT_POINTS = 512 # FFT 点数
BAR_NUM = 48 # 频谱柱数量
BAR_GAP = 2 # 柱子之间的间隔
BAR_MAX_HEIGHT = 170 # 频谱柱最大高度
BAR_BASE_Y = 215 # 频谱柱底部位置
NOISE_GATE = 2 # 噪声门限,小于该值的频谱幅度直接忽略
AMP_SCALE = 2.2 # 频谱放大倍率,数值越大柱子越高
SMOOTH_FACTOR = 0.65 # 平滑系数,越大越稳定,越小越灵敏
PEAK_DROP_SPEED = 3 # 峰值下降速度
BG_COLOR = [0, 0, 0]
BAR_COLOR = [0, 220, 255]
PEAK_COLOR = [255, 180, 0]
TEXT_COLOR = [255, 255, 255]
GRID_COLOR = [40, 40, 40]
# =========================
# I2S 麦克风引脚配置
# =========================
I2S_D0_PIN = 34
I2S_WS_PIN = 33
I2S_SCLK_PIN = 32
fm.register(I2S_D0_PIN, fm.fpioa.I2S0_IN_D0, force=True)
fm.register(I2S_WS_PIN, fm.fpioa.I2S0_WS, force=True)
fm.register(I2S_SCLK_PIN, fm.fpioa.I2S0_SCLK, force=True)
# =========================
# 初始化 LCD
# =========================
lcd.init(freq=15000000)
img = image.Image(size=(LCD_WIDTH, LCD_HEIGHT))
# =========================
# 初始化 I2S 音频输入
# =========================
rx = I2S(I2S.DEVICE_0)
rx.channel_config(rx.CHANNEL_0, rx.RECEIVER, align_mode=I2S.STANDARD_MODE)
rx.set_sample_rate(SAMPLE_RATE)
# =========================
# 频谱显示缓存
# =========================
bar_width = int((LCD_WIDTH - (BAR_NUM - 1) * BAR_GAP) / BAR_NUM)
smooth_values = []
peak_values = []
for i in range(BAR_NUM):
smooth_values.append(0)
peak_values.append(0)
# =========================
# 绘图函数
# =========================
def draw_background():
"""绘制背景、标题和辅助刻度线"""
img.clear()
img.draw_string(8, 5, "CanMV K210 FFT Audio Spectrum", color=TEXT_COLOR, scale=1)
img.draw_line(0, BAR_BASE_Y, LCD_WIDTH, BAR_BASE_Y, color=GRID_COLOR, thickness=1)
for y in range(60, BAR_BASE_Y, 40):
img.draw_line(0, y, LCD_WIDTH, y, color=GRID_COLOR, thickness=1)
def draw_status(volume_level):
"""绘制声音强度状态"""
if volume_level < 15:
status = "Sound: Low"
elif volume_level < 50:
status = "Sound: Normal"
else:
status = "Sound: High"
img.draw_string(8, 25, status, color=TEXT_COLOR, scale=1)
def draw_spectrum(values):
"""绘制频谱柱和峰值线"""
for i in range(BAR_NUM):
x = i * (bar_width + BAR_GAP)
value = values[i]
if value < NOISE_GATE:
value = 0
height = int(value * AMP_SCALE)
if height > BAR_MAX_HEIGHT:
height = BAR_MAX_HEIGHT
smooth_values[i] = int(smooth_values[i] * SMOOTH_FACTOR + height * (1 - SMOOTH_FACTOR))
if smooth_values[i] > peak_values[i]:
peak_values[i] = smooth_values[i]
else:
peak_values[i] -= PEAK_DROP_SPEED
if peak_values[i] < 0:
peak_values[i] = 0
bar_height = smooth_values[i]
bar_x = x
bar_y = BAR_BASE_Y - bar_height
peak_y = BAR_BASE_Y - peak_values[i]
if bar_height > 0:
img.draw_rectangle(
(bar_x, bar_y, bar_width, bar_height),
BAR_COLOR,
1,
True
)
if peak_values[i] > 0:
img.draw_rectangle(
(bar_x, peak_y, bar_width, 2),
PEAK_COLOR,
1,
True
)
def calc_volume(values):
"""根据频谱幅度估算当前声音强度"""
total = 0
for i in range(BAR_NUM):
total += values[i]
return int(total / BAR_NUM)
# =========================
# 主循环
# =========================
def loop():
while True:
audio = rx.record(SAMPLE_POINTS)
fft_res = FFT.run(audio.to_bytes(), FFT_POINTS)
fft_amp = FFT.amplitude(fft_res)
draw_background()
volume = calc_volume(fft_amp)
draw_status(volume)
draw_spectrum(fft_amp)
lcd.display(img)
fft_amp.clear()
time.sleep_ms(10)
# =========================
# 程序入口
# =========================
if __name__ == '__main__':
loop()
这段程序可以分成三个层级。底层是 I2S 引脚映射、LCD 初始化和音频输入初始化,负责让硬件进入可用状态;中间层是 FFT 计算和频谱幅度处理,负责把音频采样转换成适合显示的数据;上层是背景绘制、状态显示、频谱柱绘制和 LCD 刷新,负责把数据变成可观察的画面。
参数配置区决定了频谱显示的整体效果。SAMPLE_RATE 控制音频采样率,SAMPLE_POINTS 控制每次读取的采样数据量,FFT_POINTS 控制频谱计算点数,BAR_NUM 决定屏幕上显示多少根频谱柱。NOISE_GATE、AMP_SCALE、SMOOTH_FACTOR 和 PEAK_DROP_SPEED 则用于调整视觉效果,避免频谱柱太低、太高、太抖或峰值线下降过快。
| 函数 / 参数 | 功能 | 对应现象 |
|---|---|---|
draw_background() |
绘制背景、标题和辅助刻度线 | LCD 上显示黑色背景、标题、底部基准线和横向网格线 |
draw_status(volume_level) |
根据声音强度绘制状态文字 | 屏幕左上角显示 Sound: Low、Sound: Normal 或 Sound: High |
draw_spectrum(values) |
绘制频谱柱和峰值线 | 48 根频谱柱随声音变化跳动,顶部峰值线缓慢下降 |
calc_volume(values) |
估算当前声音强度 | 根据频谱平均幅度生成声音强度状态 |
loop() |
主循环采集音频并刷新屏幕 | 程序持续读取麦克风数据,实时更新 LCD 频谱画面 |
NOISE_GATE |
过滤低幅度噪声 | 环境安静时减少频谱柱轻微抖动 |
AMP_SCALE |
放大频谱幅度 | 让柱子高度更明显 |
SMOOTH_FACTOR |
平滑柱子高度 | 让画面更稳定,减少剧烈抖动 |
PEAK_DROP_SPEED |
控制峰值线下降速度 | 数值越大下降越快,数值越小保持时间越长 |
draw_spectrum() 是本实验最核心的显示函数。它会遍历 48 根频谱柱,从 FFT 幅度数组中取出对应数据。如果幅度小于 NOISE_GATE,程序直接把它当作 0 处理,减少环境底噪造成的轻微抖动。经过 AMP_SCALE 放大后,柱子高度会被限制在 BAR_MAX_HEIGHT 以内,避免图形超出屏幕区域。平滑计算使用当前高度和历史高度加权融合,让画面更稳定。峰值线则记录柱子曾经到达过的高度,在声音变小时逐步下降,增强了频谱显示的动态感。
主循环 loop() 体现了完整的数据流。rx.record(SAMPLE_POINTS) 从 I2S 麦克风读取一段音频采样,FFT.run() 把音频字节数据送入频谱计算,FFT.amplitude() 得到幅度数组。程序清空画布并重新绘制背景、声音状态、频谱柱和峰值线,再刷新到 LCD。fft_amp.clear() 用于释放幅度数据,time.sleep_ms(10) 控制循环节奏,让程序保持连续刷新但不会无间隔占满运行资源。
扩展应用
声音频谱实验涉及音频输入、算法计算和屏幕显示,问题排查需要围绕 I2S 引脚、采样参数、麦克风供电、LCD 初始化和频谱显示参数展开。画面异常时不应只检查绘图函数,也需要确认麦克风是否真的采集到了有效音频数据。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| LCD 没有画面 | LCD 初始化异常、屏幕连接异常、程序未运行 | 检查 lcd.init() 是否正常执行,确认屏幕接口和开发板供电状态 |
| 屏幕有标题但频谱不动 | I2S 麦克风未接好、引脚映射不匹配、音频输入无数据 | 核对物理引脚 34、33、32 是否对应数据线、WS 线和 SCLK 线 |
| 频谱柱一直很高 | 环境噪声较大、放大倍率过高、噪声门限过低 | 适当提高 NOISE_GATE,或降低 AMP_SCALE |
| 频谱柱几乎不动 | 麦克风信号弱、放大倍率过低、采样数据异常 | 适当增大 AMP_SCALE,靠近声源测试,确认 I2S 麦克风方向和供电 |
| 画面抖动明显 | 平滑系数过低、环境声音变化过快 | 提高 SMOOTH_FACTOR,让频谱柱变化更稳定 |
| 峰值线下降太快或太慢 | PEAK_DROP_SPEED 参数不合适 |
数值越大下降越快,数值越小峰值保持时间越长 |
| 运行一段时间后卡顿 | 刷新频率过高、计算量较大、内存释放不及时 | 保留 fft_amp.clear(),适当增加 time.sleep_ms() 的延时 |
| 程序提示 I2S 或 FFT 相关错误 | 固件环境不支持对应模块,或导入方式不匹配 | 确认 CanMV 固件和示例环境支持 maix.I2S 与 maix.FFT |
声音频谱实验的意义不止在于屏幕上显示跳动柱状图,它展示了一条完整的声音数据处理路径。真实世界中的声音先通过麦克风变成数字采样,再经过 FFT 转换成频率分布,程序根据频谱幅度绘制可视化界面。这个模式可以延伸到声音监测、课堂演示、音量提示、音乐互动装置和边缘 AI 音频识别等方向。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 音乐频谱显示器 | 使用 FFT 幅度绘制动态柱状图和峰值线 | 可增加不同颜色主题、频段分组和节奏动画 |
| 环境声音监测 | 通过 calc_volume() 估算当前声音强度 |
可扩展为噪声提醒或声音等级记录 |
| 音频教学演示 | 把声音采样、FFT 和频谱显示放到同一屏幕观察 | 可用于讲解时域、频域、采样率和频谱幅度 |
| 声音交互装置 | 根据音量高低或频段变化触发不同反馈 | 后续可扩展 LED、蜂鸣器或屏幕动画联动 |
| 设备状态提示 | 使用声音强度状态判断环境是否过于嘈杂 | 可扩展为会议室噪声提示或课堂音量提醒 |
| 简易音频特征提取 | 利用频谱幅度作为声音特征数据 | 后续可扩展 AI 声音分类或关键词识别 |
| 可视化调试工具 | 在 LCD 上实时观察麦克风输入变化 | 可用于排查麦克风连接、采样参数和声音输入质量 |
从工程角度看,当前程序已经具备较好的模块化基础。音频采集、FFT 计算、背景绘制、状态显示、频谱绘制和主循环调度各自独立,后续修改其中某一部分时不会影响整段代码结构。比如更换显示样式时,主要调整 draw_spectrum();修改声音状态判断时,主要调整 draw_status() 和 calc_volume();扩展外设联动时,可以在主循环中根据 volume 或某个频段幅度触发新的硬件动作。
总结
本实验通过 CanMV K210 完成了 I2S 麦克风音频采集、FFT 频谱计算和 LCD 实时可视化显示。代码涉及 I2S 引脚映射、音频接收配置、采样率设置、FFT 幅度计算、图像绘制、列表缓存、平滑滤波、峰值保持和循环刷新等核心能力。声音不再只是听觉信息,而是被程序转换成可计算的数据,再通过 LCD 变成可观察的频谱画面。
这类实验非常适合作为 AI 硬件音频方向的入门案例。采样数据体现了传感器读取能力,FFT 体现了基础信号处理能力,频谱柱和峰值线体现了可视化表达能力。后续课程可以在此基础上继续扩展 LED 声光联动、蜂鸣器反馈、音量阈值报警、声音分类识别、关键词唤醒、数据上传和 Web 远程监控等方向。只要理解了声音数据从采集到显示的流程,更多音频智能硬件应用都可以建立在同一套思路之上。