12 PDM简介
PDM全称pulse density modulation,即脉冲密度调制。
PDM模块处理来自外部音频前端(如数字麦克风)的脉冲密度调制信号的输入。该模块生成PDM时钟,支持双通道数据输入。
12.2 使用方法
12.2.1 方法概述
PDM使用方法分为PDM结合I2s使用和PDM数据直接DMA搬运两种。
PDM结合I2s:
1. PDM引脚GPIO配置(时钟、数据)
2. 外部时钟配置,使其处于工作模式
3. 写相应配置寄存器
4. 配置I2s数据源为PDM,配置I2s时钟、寄存器、中断
5. 使能PDM,使能I2s
6. 等待I2s中断产生
7. 读取状态寄存器,读取RX_MEM中数据
8. 传输完毕,关闭PDM和I2S
PDM数据DMA搬运:
1. PDM引脚GPIO配置(时钟、数据)
2. 外部时钟配置,使其处于工作模式
3. 写相应配置寄存器
4. 配置DMA寄存器、中断
5. 使能DMA,使能PDM
6. 等待DMA中断产生
7. 传输完毕,关闭PDM和DMA
其中I2s和DMA相关配置不在本节介绍内容范围内,可参考本手册对应章节。
12.3 编程指南
12.3.2 代码示例
下面以PDM结合I2s使用和PDM数据直接DMA搬运两种方式来展示PDM的具体使用方法。
已知现有mic使用的时钟频率为3M,I2s采样率16K。
12.3.2.1 PDM结合I2s:
#define PDM_PIN_MCLK        28
#define PDM_PIN_IN          29
static uint32_t I2s_isr(void *user_data)
{ 
    uint32_t state = I2S_GetIntState(APB_I2S);
    I2S_ClearIntState(APB_I2S, state);
    int i = I2S_GetRxFIFOCount(APB_I2S);
    while (i) {
        // do something with these data
        i--;
    }
}
void audio_input_setup(void)
{
    // GPIO & Pin Ctrl
    PINCTRL_SetPadMux(PDM_PIN_MCLK, IO_SOURCE_PDM_DMIC_MCLK);
    PINCTRL_SetPadMux(PDM_PIN_IN, IO_SOURCE_PDM_DMIC_IN);
    PINCTRL_SelPdmIn(PDM_PIN_IN);
    // PDM clock configuration, 3M
    SYSCTRL_SetPdmClkDiv(8, 1);
    SYSCTRL_SelectTypeAClk(SYSCTRL_ITEM_APB_PDM, SYSCTRL_CLK_ADC_DIV);
    
    // PDM register configuration
    PDM_Config(APB_PDM, 0, 1, 1, 1, 0);    
    // I2s configuration, bclk=2.4M, samplerate=16K, data from PDM
    I2S_DataFromPDM(1);    
    I2S_ConfigClk(APB_I2S, 5, 75);
    I2S_ConfigIRQ(APB_I2S, 0, 1, 0, 10);
    I2S_DMAEnable(APB_I2S, 0, 0);    
    I2S_Config(APB_I2S, I2S_ROLE_MASTER, I2S_MODE_STANDARD, 0, 1, 0, 1, 24);
    
    // I2s interruption
    platform_set_irq_callback(PLATFORM_CB_IRQ_I2S, I2s_isr, 0);
    // enable I2s and PDM
    I2S_ClearRxFIFO(APB_I2S);
    PDM_Start(APB_PDM, 1);
    I2S_Enable(APB_I2S, 0, 1);
}上面示例涉及到的关于I2s配置参考手册的I2s章节:
12.3.2.2 PDM数据DMA搬运
#define PDM_PIN_MCLK        28
#define PDM_PIN_IN          29
#define CHANNEL_ID          0
uint32_t buff[80];
DMA_Descriptor test __attribute__((aligned (8)));
static uint32_t DMA_cb_isr(void *user_data)
{ 
    uint32_t state = DMA_GetChannelIntState(CHANNEL_ID);
    DMA_ClearChannelIntState(CHANNEL_ID, state);
    DMA_EnableChannel(CHANNEL_ID, &test);
    // do something with data in buff
}
void DMA_SetUp(void)
{
    DMA_Reset(1);
    platform_set_irq_callback(PLATFORM_CB_IRQ_DMA, DMA_cb_isr, 0);
    test.Next = NULL;
    DMA_PreparePeripheral2RAM(&test, 
                              buff, 
                              SYSCTRL_DMA_PDM, 
                              80, 
                              DMA_ADDRESS_INC, 
                              0 | 1 << 24);
    DMA_EnableChannel(CHANNEL_ID, &test);
}
void audio_input_setup(void)
{
    //GPIO & Pin Ctrl
    PINCTRL_SetPadMux(PDM_PIN_MCLK, IO_SOURCE_PDM_DMIC_MCLK);
    PINCTRL_SetPadMux(PDM_PIN_IN, IO_SOURCE_PDM_DMIC_IN);
    PINCTRL_SelPdmIn(PDM_PIN_IN);
    // PDM clock configuration, 3M
    SYSCTRL_SetAdcClkDiv(8);
    SYSCTRL_SelectTypeAClk(SYSCTRL_ITEM_APB_PDM, SYSCTRL_CLK_ADC_DIV);
    
    // PDM register configuration
    PDM_Config(APB_PDM, 0, 1, 1, 0, 0);    
    PDM_DmaEnable(APB_PDM, 1, 0);
    
    // DMA setup
    DMA_SetUp();
    
    // enable DMA and PDM
    PDM_DmaEnable(APB_PDM, 1, 1);
    PDM_Start(APB_PDM, 1);
}建议采用DMA乒乓搬运的方法进行数据搬运,具体讲解参考手册DMA章节。