【CanMV K210】音频实验 I2S 数字麦克风与 FFT 频谱可视化

声音在程序里通常是一串采样数据,在硬件实验里却可以变成一组实时跳动的频谱柱。这个实验的价值不只是让 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.I2Smaix.FFTlcdimage,分别承担音频采集、频谱计算、屏幕刷新和图像绘制任务。

接线关系可以先通过下面的图片建立整体印象。当前实验重点关注 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.I2Smaix.FFTlcdimage 等模块 固件环境需要支持 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_GATEAMP_SCALESMOOTH_FACTORPEAK_DROP_SPEED 则用于调整视觉效果,避免频谱柱太低、太高、太抖或峰值线下降过快。

函数 / 参数 功能 对应现象
draw_background() 绘制背景、标题和辅助刻度线 LCD 上显示黑色背景、标题、底部基准线和横向网格线
draw_status(volume_level) 根据声音强度绘制状态文字 屏幕左上角显示 Sound: LowSound: NormalSound: 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.I2Smaix.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 远程监控等方向。只要理解了声音数据从采集到显示的流程,更多音频智能硬件应用都可以建立在同一套思路之上。

相关推荐
国产电子元器件1 小时前
ACS758国产替代方案有哪些?工程师如何选择霍尔电流传感器
人工智能
AI棒棒牛1 小时前
RT-DETR最新创新改进系列:2D轻量解码结构重塑检测颈部,减少下采样链路,降低计算冗余,让端到端检测更快更轻!【轻装上阵,实时优先】
人工智能·深度学习·目标检测·计算机视觉·rt-detr
陆水A1 小时前
运输时效预测模型:静态路由时效的计算与验证
大数据·人工智能·算法·spark·数据库开发·etl工程师
小小AK1 小时前
旺店通与金蝶云星空系统对接方案
人工智能
冰西瓜6001 小时前
深度学习的数学原理(三十四)—— Transformer 解码器完整实现
人工智能·深度学习·transformer
央链知播1 小时前
中国移联AI元宇宙产业委调研阿尔特汽车科技园 构建高精尖产业的“技术-场景-商业”融合生态
人工智能·汽车·业界资讯
2601_949499941 小时前
芯瑞科技400G VR4 OSFP光模块:赋能AI智算中心,破解算力互联痛点
人工智能·科技
扬帆破浪1 小时前
免费开源AI软件.桌面单机版,可移动的AI知识库,察元 AI桌面版:本地离线知识库的真完全离线 内网无外网装察元AI的拼装步骤
人工智能·windows·开源·电脑·知识图谱
SZLSDH1 小时前
企业AI的“系统化”时刻:从单点智能体到协同集群的演进逻辑
人工智能·数据可视化