Analog Microphone

本示例实现采集 AMIC (Analog Microphone) 数据,并经 CODEC 编码为 PCM 语音数据。

AMIC 采集模拟语音数据,经 CODEC 编码,送到 I2S 接收 FIFO,利用 DMA 将数据搬运到 UART

PC 端通过串口助手接收 UART 传输的数据,使用音频解析软件(如 Audacity)解析数据,并播放录制的语音。

流程如下图所示。

这里应该是 CODEC 图片

CODEC AMIC 流程

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

EVB 外接 AMIC 模块,连接 H0 和 1.8V,GND 和 GND,P2_6 和 MIC_P,P2_7 和 MIC_N;

EVB 外接 FT232 模块,连接 P3_2(UART TX)和 FT232 的 RX,P3_3(UART RX)和 FT232 的 TX。

编译和下载

该示例的工程路径如下:

Project file: board\evb\io_sample\CODEC\AMIC\mdk

Project file: board\evb\io_sample\CODEC\AMIC\gcc

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译 APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MP Tool 给出的步骤将 app bin 烧录至 EVB 内。

  5. 按下 reset 按键,开始运行。

测试验证

准备阶段

  1. 启动 PuTTY 或 UartAssist 等 PC 终端,连接到使用的 COM 端口,并进行以下 UART 设置:

    • 波特率: 3000000

    • 8 数据位

    • 1 停止位

    • 无校验

    • 无硬件流控

  2. 启动音频解析软件。

测试阶段

  1. 开始录音到录音完成,采集到的语音数据,Codec 编码为 PCM 数据后通过 UART 发送到 PC 终端。

  2. 从 PC 终端复制解码数据到新建 ASCII bin 文档中。

  3. 使用音频解析软件查看解码后的语音波形,也可以播放语音。

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

  • 工程路径: sdk\board\evb\io_sample\CODEC\AMIC

  • 源码路径: sdk\src\sample\io_sample\CODEC\AMIC

该工程的工程文件代码结构如下:

└── Project: codec_amic
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            ├── ROM.lib
            └── adc.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_gdma.c
            ├── rtl876x_uart.c
            ├── rtl876x_codec.c
            └── rtl876x_i2s.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            └── main.c

初始化

当 EVB 复位启动时,执行 main 函数,执行以下流程:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    __enable_irq();

    codec_demo();

    while (1)
    {
        ;

    }
}

codec_demo 中,包含了 PAD/PINMUX 设置,I2S 外设的初始化,UART 外设的初始化,DMA 外设的初始化,CODEC 外设的初始化等流程。

void codec_demo(void)
{
    board_codec_init();
    board_uart_init();
    driver_i2s_init();
    driver_uart_init();
    driver_gdma_init();
    driver_codec_init();
    I2S_Cmd(I2S0, I2S_MODE_TX | I2S_MODE_RX, ENABLE);
    GDMA_Cmd(GDMA_Channel_AMIC_NUM, ENABLE);
}

board_codec_init 为 AMIC 相关引脚的对 PAD 和 PINMUX 的设置,包含如下流程:

  1. 配置 PAD:设置引脚、SW 模式、NOT PowerOn、无内部上拉。其中 H0 引脚输出高,其他引脚输出失能。

void board_codec_init(void)
{
    Pad_Config(H_0, PAD_SW_MODE, PAD_NOT_PWRON, PAD_PULL_NONE, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    Pad_Config(P2_6, PAD_SW_MODE, PAD_NOT_PWRON, PAD_PULL_NONE, PAD_OUT_DISABLE, PAD_OUT_HIGH);
    Pad_Config(P2_7, PAD_SW_MODE, PAD_NOT_PWRON, PAD_PULL_NONE, PAD_OUT_DISABLE, PAD_OUT_LOW);
}

board_uart_init 为 UART 相关引脚对 PAD 和 PINMUX 的设置,包含如下流程:

  1. 配置 PAD:设置引脚、PINMUX 模式、PowerOn、内部上拉。TX 引脚输出高,RX 引脚输出失能。

  2. 配置 PINMUX:分配引脚分别为 UART0_TX 和 UART0_RX。

void board_uart_init(void)
{
    Pad_Config(UART_TX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_ENABLE, PAD_OUT_HIGH);
    Pad_Config(UART_RX_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);

    Pinmux_Config(UART_TX_PIN, UART0_TX);
    Pinmux_Config(UART_RX_PIN, UART0_RX);
}

driver_i2s_init 为 I2S 外设的初始化,包含如下流程:

  1. 使能 RCC 时钟。

  2. 设置 I2S 时钟源为 40MHz,采样率为 16kHz。

  3. 设置为主设备模式,单声道输出。

void driver_i2s_init(void)
{
    RCC_PeriphClockCmd(APBPeriph_I2S0, APBPeriph_I2S0_CLOCK, ENABLE);

    I2S_InitTypeDef I2S_InitStruct;
    I2S_StructInit(&I2S_InitStruct);
    I2S_InitStruct.I2S_ClockSource      = I2S_CLK_40M;
    I2S_InitStruct.I2S_DataFormat       = I2S_Mode;
    I2S_InitStruct.I2S_ChannelType      = I2S_Channel_Mono;
    I2S_InitStruct.I2S_DeviceMode       = I2S_DeviceMode_Master;
    I2S_InitStruct.I2S_RxWaterlevel     = 0x4;
    /* BCLK = 40MHz*(I2S_BClockNi/I2S_BClockMi) = Sample Rate * 64BCLK/sample */
    I2S_InitStruct.I2S_BClockMi         = 0x186A;
    I2S_InitStruct.I2S_BClockNi         = 0xA0;

    I2S_Init(I2S0, &I2S_InitStruct);
}

driver_uart_init 为 UART 外设的初始化,包含如下流程:

  1. 使能 RCC 时钟源。

  2. 设置 UART 波特率为 3M。

  3. 使能 UART TX DMA 功能。

void driver_uart_init(void)
{
    /* Enable clock */
    RCC_PeriphClockCmd(APBPeriph_UART0, APBPeriph_UART0_CLOCK, ENABLE);

    /* UART init */
    UART_InitTypeDef UART_InitStruct;

    UART_StructInit(&UART_InitStruct);
    /* change to 3M baudrate */
    UART_InitStruct.UART_Div             = 1;
    UART_InitStruct.UART_Ovsr            = 8;
    UART_InitStruct.UART_OvsrAdj        = 0x492;
    UART_InitStruct.UART_TxWaterLevel    = 12;
    UART_InitStruct.UART_TxDmaEn         = ENABLE;
    UART_InitStruct.UART_DmaEn           = UART_DMA_ENABLE;
    UART_Init(UART0, &UART_InitStruct);
}

driver_gdma_init 为 DMA 外设的初始化,包含如下流程:

  1. 使用 DMA 通道 0。

  2. DMA 的传输方向为外设到外设传输。

  3. 源端地址为 (&(I2S0->RX_DR)) ,目的端地址为 (&(UART0->RB_THR))

  4. 设置源端数据宽度为 32 位,目的端数据宽度为 8 位。

  5. 设置源端单次 burst 传输数据的个数为 1,目的端单次 burst 传输数据的个数为 4。

  6. 使能 DMA 通道 0 总传输完成中断 GDMA_INT_Transfer

void driver_gdma_init(void)
{
    /* Enable GDMA clock */
    RCC_PeriphClockCmd(APBPeriph_GDMA, APBPeriph_GDMA_CLOCK, ENABLE);

    /* Initialize GDMA peripheral */
    GDMA_InitTypeDef GDMA_InitStruct;

    GDMA_StructInit(&GDMA_InitStruct);
    GDMA_InitStruct.GDMA_ChannelNum          = GDMA_Channel_AMIC_NUM;
    GDMA_InitStruct.GDMA_DIR                 = GDMA_DIR_PeripheralToPeripheral;
    GDMA_InitStruct.GDMA_BufferSize          = AUDIO_FRAME_SIZE / 4;
    GDMA_InitStruct.GDMA_SourceInc           = DMA_SourceInc_Fix;
    GDMA_InitStruct.GDMA_DestinationInc      = DMA_DestinationInc_Fix;
    GDMA_InitStruct.GDMA_SourceDataSize      = GDMA_DataSize_Word;
    GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_Byte;
    GDMA_InitStruct.GDMA_SourceMsize         = GDMA_Msize_1;
    GDMA_InitStruct.GDMA_DestinationMsize    = GDMA_Msize_4;
    GDMA_InitStruct.GDMA_SourceAddr          = (uint32_t)(&(I2S0->RX_DR));
    GDMA_InitStruct.GDMA_DestinationAddr     = (uint32_t)(&(UART0->RB_THR));
    GDMA_InitStruct.GDMA_SourceHandshake     = GDMA_Handshake_I2S0_RX;
    GDMA_InitStruct.GDMA_DestHandshake       = GDMA_Handshake_UART0_TX;
    GDMA_Init(GDMA_Channel_AMIC, &GDMA_InitStruct);
    GDMA_INTConfig(GDMA_Channel_AMIC_NUM, GDMA_INT_Transfer, ENABLE);

    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = GDMA_Channel_AMIC_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

driver_codec_init 为 CODEC 外设的初始化,包含如下流程:

  1. 初始化模拟电路相关配置。

  2. 使能 RCC 时钟。

  3. 麦克风类型选择 AMIC。

  4. 设置 MICBST 模式为差分模式。

  5. 设置采样率为 16kHz。

  6. 设置数据格式为 I2S 格式。

  7. 设置数据宽度为 16 位。

void driver_codec_init(void)
{
    /*switch to pwm mode*/
    SystemCall(3, 1);
    CODEC_AnalogCircuitInit();
    RCC_PeriphClockCmd(APBPeriph_CODEC, APBPeriph_CODEC_CLOCK, ENABLE);

    CODEC_InitTypeDef CODEC_InitStruct;
    CODEC_StructInit(&CODEC_InitStruct);

    CODEC_InitStruct.CODEC_Ch0MicType       = CODEC_CH0_AMIC;
    CODEC_InitStruct.CODEC_MicBstMode       = MICBST_Mode_Differential;
    CODEC_InitStruct.CODEC_SampleRate       = SAMPLE_RATE_16KHz;
    CODEC_InitStruct.CODEC_I2SFormat        = CODEC_I2S_DataFormat_I2S;
    CODEC_InitStruct.CODEC_I2SDataWidth     = CODEC_I2S_DataWidth_16Bits;
    CODEC_Init(CODEC, &CODEC_InitStruct);

    CODEC_Init(CODEC, &CODEC_InitStruct);
}

功能实现

  1. 初始化完毕后,使能 I2S 发送模式和接收模式;使能 DMA 通道 0 传输。

void codec_demo(void)
{
    ...
    I2S_Cmd(I2S0, I2S_MODE_TX | I2S_MODE_RX, ENABLE);
    GDMA_Cmd(GDMA_Channel_AMIC_NUM, ENABLE);
}
  1. AMIC 采集到模拟语音数据,经 CODEC 解码,送到 I2S0 接收 FIFO,利用 DMA 将数据搬运到 UART5;当数据传输完成时,触发 GDMA_INT_Transfer 中断,进入中断处理函数 GDMA_Channel_AMIC_Handler

    1. 重新设置源端地址和目的端地址。

    2. 重新设置 DMA 传输数据大小。

    3. 清除 DMA 通道 0 GDMA_INT_Transfer 中断挂起位,使能 DMA 通道 0。

    void GDMA_Channel_AMIC_Handler(void)
    {
        GDMA_SetSourceAddress(GDMA_Channel_AMIC, (uint32_t)(&(I2S0->RX_DR)));
        GDMA_SetDestinationAddress(GDMA_Channel_AMIC, (uint32_t)(&(UART0->RB_THR)));
    
        GDMA_SetBufferSize(GDMA_Channel_AMIC, AUDIO_FRAME_SIZE / 4);
    
        GDMA_ClearINTPendingBit(GDMA_Channel_AMIC_NUM, GDMA_INT_Transfer);
        GDMA_Cmd(GDMA_Channel_AMIC_NUM, ENABLE);
    }