这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » M5STACKTAB5开发指南【音频开发之录音机】

共1条 1/1 1 跳转至

M5STACKTAB5开发指南【音频开发之录音机】

助工
2026-01-14 22:17:25     打赏

一、硬件介绍

Tab5 集成了双芯片架构和丰富的硬件资源,其主控采用基于 RISC‑V 架构的 ESP32‑P4 SoC,并配备 16MB Flash 与 32MB PSRAM,无线模块则选用 ESP32-C6-MINI-1U,支持 Wi-Fi 6;

还配备5英寸(1280×720 IPS)触控屏幕,以及2MP摄像头(1600×1200)、双麦克风阵列,3.5mm耳机孔与扬声器;


内置BMI270六轴传感器、实时时钟,板载HY2.0-4P,M5-Bus,GPIO_EXT排母和microSD卡槽等;

底部兼容NP‑F550可拆卸锂电池(具备充放电与实时监测电路);

image-20260104213126322




image-20260104213126322.png


image-20260104223329802二、功能实现

1、硬件介绍

麦克风 / 扬声器

Tab5的麦克风采用双麦克风系统(AEC 回声消除)、扬声器由1W 8Ω NS4150B驱动;


双麦克风阵列由两个芯片协作控制:


ES7210:作为AEC回声消除前端芯片,负责麦克风音频的采集与预处理,通过I2C总线(地址0x40)与ESP32-P4主控通信;


ES8388:作为音频编解码芯片,负责对麦克风采集的信号进行编码处理,通过I2C总线(地址0x10)与ESP32-P4主控通信;


NS4150B 功率放大器:将ES8388输出的模拟音频信号进行功率放大,驱动板载扬声器发声,最大输出功率1W;



硬件引脚连接


image-20260112153247672.png


image-20260112153306612.png


image-20260112153247672

原理图


image-20260112144103821.png

image-20260112144143998.png



2、功能效果


实现效果:主要通过LCD屏幕、板载麦克风 / 扬声器实现,通过触摸屏幕的UI按钮实现音频录制 / 播放的录音机功能;



默认为60s缓存录音,重新录音会清空;


底部两个UI按钮(录制 / 停止、播放 / 停止播放),在录制 / 播放状态时按钮颜色与文本会相应变化;


录音时每秒更新状态 “录制中: _s / 60s”,播放时显示 “播放中: _s / 总秒数”;

(播放时可点击停止播放,播放完成后恢复界面)



image-20260114002955297.png





三、代码编写



通过修改相关宏定义参数,可配置不同录制的音频效果;


1、板载PSRAM为32MB;

当前音频内存占用:RECORD_SIZE * 2【16bit】 = 1.95 MB;

最长录制时间:(32 × 1024 × 1024 / 2) / RECORD_SAMPLERATE = 16min27s ;


2、若需更长录制的时间,可将音频存储到SD卡中,再进行读取等操作实现;



#include <M5Unified.h>

// 音量播放大小
#define VOLUME 255

// 音频参数
#define RECORD_LENGTH 720       // 每块采样数
#define RECORD_SAMPLERATE 17000 // 采样率:17kHz
#define MAX_RECORD_SEC 60       // 最大录制时长:60秒 (可根据PSRAM调整 / 存储到SD卡)
#define MAX_SAMPLES (RECORD_SAMPLERATE * MAX_RECORD_SEC)
#define MAX_CHUNKS ((MAX_SAMPLES + RECORD_LENGTH - 1) / RECORD_LENGTH)
#define RECORD_SIZE (MAX_CHUNKS * RECORD_LENGTH)


static int16_t *rec_data;

//  录制状态
static bool is_recording = false; // 是否正在录制
static size_t rec_chunks = 0;     // 已录制块数
static size_t rec_write_idx = 0;  // 写入索引

//  UI参数
struct Rect
{
    int32_t x, y, w, h;
};

static Rect btn_rec, btn_play;
static int32_t w;
static uint32_t last_display_sec = 0;      // 记录上次显示的秒数
static uint32_t last_play_display_sec = 0; // 记录上次显示的播放秒数

// 绘制按钮
static void drawButton(const Rect &r, const char *label, uint16_t fillColor, uint16_t textColor)
{
    M5.Display.fillRoundRect(r.x, r.y, r.w, r.h, 8, fillColor);
    M5.Display.drawRoundRect(r.x, r.y, r.w, r.h, 8, TFT_WHITE);
    M5.Display.setTextDatum(middle_center);
    M5.Display.setTextColor(textColor);
    M5.Display.setFont(&fonts::efontCN_24);
    M5.Display.drawString(label, r.x + r.w / 2, r.y + r.h / 2);
}

// 触摸判断
static bool hitRect(const Rect &r, int32_t px, int32_t py)
{
    return (px >= r.x && px < (r.x + r.w) && py >= r.y && py < (r.y + r.h));
}

// UI布局
static void layoutUI()
{
    const int32_t sh = M5.Display.height();
    const int32_t sw = M5.Display.width();

    w = sw;

    // 底部两个按钮,各占据一半宽度
    const int32_t btn_w = sw / 2;   
    const int32_t btn_h = sh / 4;    
    const int32_t btn_y = sh - btn_h; 

    btn_rec = {0, btn_y, btn_w, btn_h};
    btn_play = {btn_w, btn_y, btn_w, btn_h}; 
}


static void drawStaticUI()
{
    M5.Display.fillScreen(TFT_BLACK);

    // 标题
    M5.Display.setTextDatum(top_center);
    M5.Display.setTextColor(TFT_WHITE);
    M5.Display.setFont(&fonts::efontCN_24);
    M5.Display.drawString("语音录制", w / 2, 50);

    // 绘制按钮
    drawButton(btn_rec, is_recording ? "停止" : "录制", is_recording ? TFT_RED : TFT_DARKGREY, TFT_WHITE);
    drawButton(btn_play, "播放", TFT_GREEN, TFT_WHITE);
    M5.Display.display();
}

// 开始录制
static void startRecording()
{
    if (M5.Speaker.isEnabled())
        M5.Speaker.end();
    if (!M5.Mic.isEnabled())
        M5.Mic.begin();

    is_recording = true;
    rec_chunks = 0;
    rec_write_idx = 0;
    last_display_sec = 0; // 重置显示秒数计数器

    // 清空缓冲区
    memset(rec_data, 0, RECORD_SIZE * sizeof(int16_t));

    // 清空显示区域
    M5.Display.fillRect(0, 50, w, 150, TFT_BLACK);

    // 更新按钮显示
    drawButton(btn_rec, "停止", TFT_RED, TFT_WHITE);
    M5.Display.display();
}

// 停止录制
static void stopRecording()
{
    is_recording = false;
    drawButton(btn_rec, "录制", TFT_DARKGREY, TFT_WHITE);

    // 录制结束
    M5.Display.fillRect(0, 100, w, 60, TFT_BLACK);
    M5.Display.setTextDatum(top_center);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.setFont(&fonts::efontCN_24);
    M5.Display.drawString("录制结束", w / 2, 110);
    M5.Display.display();
    delay(800);
}

// 播放录制
static void playRecording()
{
    if (is_recording)
        return;
    if (rec_chunks == 0)
    {
        M5.Display.setTextDatum(top_center);
        M5.Display.setTextColor(TFT_RED);
        M5.Display.setFont(&fonts::efontCN_24);
        M5.Display.drawString("暂无录音!", w / 2, 150);
        delay(1500);
        drawStaticUI();
        return;
    }

    while (M5.Mic.isRecording())
    {
        delay(1);
    }

    if (M5.Mic.isEnabled())
        M5.Mic.end();
    if (!M5.Speaker.isEnabled())
        M5.Speaker.begin();

    // 显示"正在播放"
    const size_t total_samples = rec_chunks * RECORD_LENGTH;
    const uint32_t total_duration = total_samples / RECORD_SAMPLERATE; // 总时长
    uint32_t play_start_time = millis();
    last_play_display_sec = 0; // 重置播放秒数

    M5.Speaker.playRaw(rec_data, total_samples, RECORD_SAMPLERATE, false, 1, 0);

    // 显示停止播放按钮
    drawButton(btn_play, "停止播放", TFT_RED, TFT_WHITE);

    // 播放时显示进度
    while (M5.Speaker.isPlaying())
    {
        M5.update();
        uint32_t elapsed_ms = millis() - play_start_time;
        uint32_t elapsed_sec = elapsed_ms / 1000;

        if (elapsed_sec != last_play_display_sec)
        {
            last_play_display_sec = elapsed_sec;
            M5.Display.fillRect(0, 80, w, 100, TFT_BLACK);
            M5.Display.setTextDatum(top_center);
            M5.Display.setTextColor(TFT_GREEN);
            M5.Display.setFont(&fonts::efontCN_24);

            char playStr[48];
            snprintf(playStr, sizeof(playStr), "播放中: %us / %us", elapsed_sec, total_duration);
            M5.Display.drawString(playStr, w / 2, 120);

            M5.Display.display();
        }

        if (M5.Touch.getCount())
        {
            auto d = M5.Touch.getDetail(0);
            if (d.wasClicked() && hitRect(btn_play, d.x, d.y))
            {
                M5.Speaker.stop();
                delay(100);
                break;
            }
        }
        delay(50);
    }

    // 重新回到录制模式
    if (M5.Speaker.isEnabled())
        M5.Speaker.end();
    if (!M5.Mic.isEnabled())
        M5.Mic.begin();

    // 显示播放完成
    M5.Display.fillRect(0, 80, w, 100, TFT_BLACK);
    M5.Display.setTextDatum(top_center);
    M5.Display.setTextColor(TFT_CYAN);
    M5.Display.setFont(&fonts::efontCN_24);
    M5.Display.drawString("播放完成", w / 2, 120);
    M5.Display.display();
    delay(1000);

    // 恢复界面
    drawStaticUI();
}

void setup(void)
{
    M5.begin();

    w = M5.Display.width();

    // 设置屏幕显示参数
    M5.Display.setRotation(1);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.setTextDatum(top_center);
    M5.Display.setTextColor(WHITE);
    M5.Display.setFont(&fonts::efontCN_24);

    // 分配音频缓冲区
    rec_data = (typeof(rec_data))heap_caps_malloc(RECORD_SIZE * sizeof(int16_t), MALLOC_CAP_SPIRAM);
    if (rec_data == nullptr)
    {
        rec_data = (typeof(rec_data))heap_caps_malloc(RECORD_SIZE * sizeof(int16_t), MALLOC_CAP_8BIT);
    }

    memset(rec_data, 0, RECORD_SIZE * sizeof(int16_t));
    M5.Speaker.setVolume(VOLUME);

    // 麦克风和扬声器不能同时使用
    M5.Speaker.end();
    M5.Mic.begin();

    // 初始化UI布局
    layoutUI();
    drawStaticUI();
    M5.Display.display();
}

void loop(void)
{
    M5.update();

    //  按钮触摸处理
    if (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked())
    {
        auto d = M5.Touch.getDetail(0);
        const int32_t tx = d.x;
        const int32_t ty = d.y;

        // 录制按钮
        if (hitRect(btn_rec, tx, ty))
        {
            if (!is_recording)
            {
                startRecording();
            }
            else
            {
                stopRecording();
            }
        }
        // 播放按钮
        else if (hitRect(btn_play, tx, ty))
        {
            playRecording();
        }
    }

    //  录制循环
    if (is_recording && M5.Mic.isEnabled())
    {
        // 检查是否达到60秒
        if (rec_write_idx >= MAX_CHUNKS)
        {
            stopRecording();
            M5.Display.setTextDatum(top_center);
            M5.Display.setTextColor(TFT_ORANGE);
            M5.Display.setFont(&fonts::efontCN_24);
            M5.Display.drawString("已达60秒上限", w / 2, 60);
            delay(1000);
            drawStaticUI();
            return;
        }

        // 录制一块数据
        auto write_ptr = &rec_data[rec_write_idx * RECORD_LENGTH];
        if (M5.Mic.record(write_ptr, RECORD_LENGTH, RECORD_SAMPLERATE))
        {
            // 更新块计数
            ++rec_write_idx;
            if (rec_chunks < rec_write_idx)
            {
                rec_chunks = rec_write_idx;
            }

            // 显示录制时间
            const uint32_t recorded_sec = (uint32_t)((rec_chunks * RECORD_LENGTH) / RECORD_SAMPLERATE);

            if (recorded_sec != last_display_sec)
            {
                last_display_sec = recorded_sec;
                M5.Display.fillRect(0, 100, w, 60, TFT_BLACK);
                M5.Display.setTextDatum(top_center);
                M5.Display.setTextColor(TFT_YELLOW);
                M5.Display.setFont(&fonts::efontCN_24);
                char timeStr[32];
                snprintf(timeStr, sizeof(timeStr), "录制中: %us / 60s", recorded_sec);
                M5.Display.drawString(timeStr, w / 2, 110);
                M5.Display.display();
            }

            if (rec_write_idx >= MAX_CHUNKS)
            {
                stopRecording();
                M5.Display.setTextDatum(top_center);
                M5.Display.setTextColor(TFT_ORANGE);
                M5.Display.setFont(&fonts::efontCN_24);
                M5.Display.drawString("已达60秒上限", w / 2, 150);
                delay(1000);
                drawStaticUI();
            }
        }
    }
}







四、程序烧录





1、连接USB数据线至开发板;

2、选择端口号对应的开发板;

3、点击 上传 烧录程序到开发板上;

image-20260105170611018.png


image-20260105170611018

五、效果演示


录制音频,以及播放效果;



效果演示












关键词: M5STACK    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]