RT-Thread组件之Audio框架i2s驱动的编写

前言

  1. rt-thread本次使用的是标志版本为5.0。
  2. 本次实现的是音频播放部分的i2s驱动
  3. 使用的芯片为stm32f407zgt6
  4. 在网上搜了一下关于rt-thread 音频部分的内容,没有实质的提供i2s对接的硬件驱动。所以才有了此次的记录,做一下分享记录。
  5. 参考的git源码驱动,这个是用的sai接口,而f4并没有
  6. 本次注重实际代码部分,关于一些音频相关的理论知识自行查阅相关资料。
  7. 使用到的音频模块为wm8978,相关驱动参考之前的文章
  8. 音频文件保存到sd中,使用的是rt-thread 的虚拟文件系统组件

rt-thread的audio框架

在这里插入图片描述

配置阶段

在这里插入图片描述在这里插入图片描述

播放阶段

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
底层驱动需要做的是,通知框架发送下一帧数据 ,不然会阻塞
在这里插入图片描述

录音阶段(本文暂未涉及)

  1. 过程跟播放是类似的

I2S 音频驱动(暂时只做了播放)

头文件

#ifndef F4_OS_DR_I2S_H
#define F4_OS_DR_I2S_H

#include <rtthread.h>
#include <rthw.h>
#include <rtdevice.h>
#include <drv_common.h>
#include <drv_dma.h>
#include <drv_config.h>

#define I2S_TX_DMA_FIFO_SIZE (4096)
#define I2S_SOUND_NAME "sound0"
#define CODEC_I2C_NAME  ("i2c1")
struct stm32_i2s_cnf {
    rt_uint32_t standard;
    rt_uint32_t mode;
    rt_uint32_t data_format;
    rt_uint32_t clk_cpl;
};

struct stm32_audio { 
    I2S_HandleTypeDef i2s;
    DMA_HandleTypeDef dma_tx;
    struct rt_audio_device audio;
    struct rt_audio_configure replay_config;
    struct stm32_i2s_cnf *i2s_conf;
    rt_int32_t replay_volume;
    rt_uint8_t *tx_fifo;
    rt_bool_t startup;
};

int rt_hw_audio_init(void);
#endif //F4_OS_DR_I2S_H

原文件

#include "board.h"
#include "drv_audio.h"
#include "audio_wm8978.h"

#define DBG_TAG              "drv.sound"
#ifdef  DRV_DEBUG
#define DBG_LVL              DBG_LOG
#else
#define DBG_LVL              DBG_LOG
#endif /* DRV_DEBUG */

#include <rtdbg.h>


struct stm32_audio _stm32_audio_play = {
        .i2s.Instance=SPI2
};

#define _i2s _stm32_audio_play.i2s
#define _dma_tx _stm32_audio_play.dma_tx
//采样率计算公式:Fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
//I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR
//一般HSE=8Mhz
//pllm:在Sys_Clock_Set设置的时候确定,一般是8
//PLLI2SN:一般是192~432
//PLLI2SR:2~7
//I2SDIV:2~255
//ODD:0/1
//I2S分频系数表@pllm=8,HSE=8Mhz,即vco输入频率为1Mhz
//表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD
const rt_uint16_t i2s_psc_tbl[][5] =
        {
                {800,   256, 5, 12, 1},        //8Khz采样率
                {1102,  429, 4, 19, 0},        //11.025Khz采样率
                {1600,  213, 2, 13, 0},        //16Khz采样率
                {2205,  429, 4, 9,  1},        //22.05Khz采样率
                {3200,  213, 2, 6,  1},        //32Khz采样率
                {4410,  271, 2, 6,  0},        //44.1Khz采样率
                {4800,  258, 3, 3,  1},        //48Khz采样率
                {8820,  316, 2, 3,  1},        //88.2Khz采样率
                {9600,  344, 2, 3,  1},    //96Khz采样率
                {17640, 361, 2, 2,  0},    //176.4Khz采样率
                {19200, 393, 2, 2,  0},    //192Khz采样率
        };
//设置SAIA的采样率(@MCKEN)
//samplerate:采样率,单位:Hz
//返回值:0,设置成功;1,无法设置.
rt_err_t I2S2_SampleRate_Set(rt_uint32_t samplerate) {

    rt_uint16_t i;
    volatile uint32_t tempreg = 0;
    RCC_PeriphCLKInitTypeDef RCCI2S2_ClkInitSture;
    //看看改采样率是否可以支持
    for (i = 0; i < (sizeof(i2s_psc_tbl) / 10); i++) {
        if ((samplerate / 10) == i2s_psc_tbl[i][0])break;
    }
    if (i == (sizeof(i2s_psc_tbl) / 10))return RT_ERROR;//搜遍了也找不到
    RCCI2S2_ClkInitSture.PeriphClockSelection = RCC_PERIPHCLK_I2S;            //外设时钟源选择
    RCCI2S2_ClkInitSture.PLLI2S.PLLI2SN = (uint32_t) i2s_psc_tbl[i][1];        //设置PLLI2SN
    RCCI2S2_ClkInitSture.PLLI2S.PLLI2SR = (uint32_t) i2s_psc_tbl[i][2];        //设置PLLI2SR
    HAL_RCCEx_PeriphCLKConfig(&RCCI2S2_ClkInitSture);                //设置时钟

    SET_BIT(RCC->CR, RCC_CR_PLLI2SON);       //开启I2S时钟
    while (READ_BIT(RCC->CR, RCC_CR_PLLI2SRDY) == 0);//等待I2S时钟开启成功.


    if (_i2s.Instance->I2SCFGR & SPI_I2SCFGR_I2SE) {
        __HAL_I2S_DISABLE(&_i2s); /*禁止i2s 配置以下寄存器 (手册上说明)*/
    }
    tempreg = i2s_psc_tbl[i][3] << 0;     //设置I2SDIV
    tempreg |= i2s_psc_tbl[i][4] << 8;    //设置ODD位
    tempreg |= 1 << 9;                    //使能MCKOE位,输出MCK
    WRITE_REG(_i2s.Instance->I2SPR, tempreg);//设置I2SPR寄存器
    if (!(_i2s.Instance->I2SCFGR & SPI_I2SCFGR_I2SE)) { /*如果i2s没有启动*/
        __HAL_I2S_ENABLE(&_i2s); /*启用i2s 配置以下寄存器 (手册上说明)*/
    }
    return RT_EOK;
}



//I2S2初始化
//I2S_Standard:I2S标准,可以设置:I2S_STANDARD_PHILIPS/I2S_STANDARD_MSB/
//				       I2S_STANDARD_LSB/I2S_STANDARD_PCM_SHORT/I2S_STANDARD_PCM_LONG
//I2S_Mode:I2S工作模式,可以设置:I2S_MODE_SLAVE_TX/I2S_MODE_SLAVE_RX/I2S_MODE_MASTER_TX/I2S_MODE_MASTER_RX
//I2S_Clock_Polarity:时钟电平,可以设置为:I2S_CPOL_LOW/I2S_CPOL_HIGH
//I2S_DataFormat:数据长度,可以设置:I2S_DATAFORMAT_16B/I2S_DATAFORMAT_16B_EXTENDED/I2S_DATAFORMAT_24B/I2S_DATAFORMAT_32B
static void
stm32_i2s_init(struct stm32_i2s_cnf *i2s_cnf) {


    _i2s.Init.Mode = i2s_cnf->mode;                    //IIS模式
    _i2s.Init.Standard = i2s_cnf->standard;            //IIS标准
    _i2s.Init.DataFormat = i2s_cnf->data_format;        //IIS数据长度
    _i2s.Init.MCLKOutput = I2S_MCLKOUTPUT_ENABLE;    //主时钟输出使能
    _i2s.Init.AudioFreq = I2S_AUDIOFREQ_DEFAULT;    //IIS频率设置
    _i2s.Init.CPOL = i2s_cnf->clk_cpl;          //空闲状态时钟电平
    _i2s.Init.ClockSource = I2S_CLOCK_PLL;        //IIS时钟源为PLL
    HAL_I2S_Init(&_i2s);
    SET_BIT(_i2s.Instance->CR2, SPI_CR2_TXDMAEN); //Tx Buffer DMA Enable
    __HAL_I2S_ENABLE(&_i2s);                    //使能I2S2
}

void audio_channel_set(rt_uint16_t channel) {
    // todo 不支持设置通道
    RT_UNUSED(channel);
}

void audio_samplerate_set(rt_uint32_t samplerate) {
    I2S2_SampleRate_Set(samplerate);
}

void audio_samplebits_set(rt_uint16_t samplebits) {
    if (_stm32_audio_play.i2s_conf) { /*如果用户自行配置的话就有用户的*/
        stm32_i2s_init(_stm32_audio_play.i2s_conf);
        return;
    }
    struct stm32_i2s_cnf i2s_cnf;
    switch (samplebits) {
        case 16:
            LOG_D("飞利浦标准,16位数据长度");
            wm8978_I2S_Cfg(2, 0);  // 飞利浦标准,主机发送,时钟低电平有效,16位帧长度
            i2s_cnf.mode = I2S_MODE_MASTER_TX;
            i2s_cnf.standard = I2S_STANDARD_PHILIPS;
            i2s_cnf.clk_cpl = I2S_CPOL_LOW;
            i2s_cnf.data_format = I2S_DATAFORMAT_16B_EXTENDED;
            break;
        case 24:
            LOG_D("飞利浦标准,24位数据长度");
            wm8978_I2S_Cfg(2, 2);     //飞利浦标准,主机发送,时钟低电平有效,24位扩展帧长度
            i2s_cnf.mode = I2S_MODE_MASTER_TX;
            i2s_cnf.standard = I2S_STANDARD_PHILIPS;
            i2s_cnf.clk_cpl = I2S_CPOL_LOW;
            i2s_cnf.data_format = I2S_DATAFORMAT_24B;
            break;
        case 32:
            wm8978_I2S_Cfg(2, 3);     //飞利浦标准,主机发送,时钟低电平有效,24位扩展帧长度
            i2s_cnf.mode = I2S_MODE_MASTER_TX;
            i2s_cnf.standard = I2S_STANDARD_PHILIPS;
            i2s_cnf.clk_cpl = I2S_CPOL_LOW;
            i2s_cnf.data_format = I2S_DATAFORMAT_32B;
            break;
        default:
            // todo
            return;
    }
    stm32_i2s_init(&i2s_cnf);
}


/* initial sai A */
rt_err_t audio_config_init() {
    rt_uint32_t temp;

    return RT_EOK;
}

static void i2s_transmit_complate(struct __DMA_HandleTypeDef *hdma);

rt_err_t audio_tx_dma(void) {
    __HAL_RCC_DMA1_CLK_ENABLE();                                //使能DMA1时钟
    __HAL_LINKDMA(&_i2s, hdmatx, _dma_tx);                      //将DMA与I2S联系起来
    _dma_tx.Instance = DMA1_Stream4;                            //DMA1数据流4
    _dma_tx.Init.Channel = DMA_CHANNEL_0;                       //通道0
    _dma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;              //存储器到外设模式
    _dma_tx.Init.PeriphInc = DMA_PINC_DISABLE;                  //外设非增量模式
    _dma_tx.Init.MemInc = DMA_MINC_ENABLE;                      //存储器增量模式
    /*todo 这里按照道理应该根据数据位宽来配置 ?*/
    _dma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //外设数据长度:16位
    _dma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;    //存储器数据长度:16位
    _dma_tx.Init.Mode = DMA_CIRCULAR;                           //使用循环模式
    _dma_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;             //高优先级
    _dma_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;               //不使用FIFO
    _dma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;       // 使用FIFO阈值完全配置
    _dma_tx.Init.MemBurst = DMA_MBURST_SINGLE;                  //存储器单次突发传输
    _dma_tx.Init.PeriphBurst = DMA_MBURST_SINGLE;               //外设突发单次传输

    HAL_DMA_DeInit(&_dma_tx);                              //先清除以前的设置
    HAL_DMA_Init(&_dma_tx);                                //初始化DMA
    __HAL_DMA_DISABLE(&_dma_tx);                                 //先关闭DMA
    /*开启dma双缓冲模式*/
    /* Enable the double buffer mode */
    _dma_tx.Instance->CR |= (uint32_t) DMA_SxCR_DBM;
    /* Configure DMA Stream destination address */
    _dma_tx.Instance->M1AR = (uint32_t) (_stm32_audio_play.tx_fifo + I2S_TX_DMA_FIFO_SIZE);
    /* Configure DMA Stream data length */
    _dma_tx.Instance->NDTR = I2S_TX_DMA_FIFO_SIZE / 2;
    /* Configure DMA Stream destination address */
    _dma_tx.Instance->PAR = (uint32_t) &(_i2s.Instance->DR);
    /* Configure DMA Stream source address */
    _dma_tx.Instance->M0AR = (uint32_t) _stm32_audio_play.tx_fifo;
    /* Clear all flags */
    __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_TC_FLAG_INDEX(&_dma_tx));
    __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_HT_FLAG_INDEX(&_dma_tx));
    __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_TE_FLAG_INDEX(&_dma_tx));
    __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_DME_FLAG_INDEX(&_dma_tx));
    __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_FE_FLAG_INDEX(&_dma_tx));

    /* Enable Common interrupts*/
    /* todo 这里是否需要开启 错误中断使能 ?*/
    //DMA_IT_TE: 传输错误中断使能 (Transfer error interrupt enable)
    //DMA_IT_DME: 直接模式错误中断使能 (Direct mode error interrupt enable)

    rt_thread_delay(20);
    //DMA_IT_TC: 传输完成中断使能 (Transfer complete interrupt enable)
    __HAL_DMA_ENABLE_IT(&_dma_tx, DMA_IT_TC);                    //开启传输完成中断
    __HAL_DMA_CLEAR_FLAG(&_dma_tx, DMA_FLAG_TCIF0_4);            //清除DMA传输完成中断标志位
    HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 1, 0);    //DMA中断优先级
    HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);

    __HAL_DMA_ENABLE(&_dma_tx);                                //打开dma
    return RT_EOK;
}


static void i2s_dma_tx_set(void) {
    __HAL_RCC_DMA1_CLK_ENABLE(); // 使能DMA1时钟
    __HAL_LINKDMA(&_i2s, hdmatx, _dma_tx);//将DMA与I2S联系起来
    _dma_tx.Instance = DMA1_Stream4;                            //DMA1数据流4
    _dma_tx.Init.Channel = DMA_CHANNEL_0;                        //通道0
    _dma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;                //存储器到外设模式
    _dma_tx.Init.PeriphInc = DMA_PINC_DISABLE;                    //外设非增量模式
    _dma_tx.Init.MemInc = DMA_MINC_ENABLE;                        //存储器增量模式
    _dma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;    //外设数据长度:16位
    _dma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;        //存储器数据长度:16位
    _dma_tx.Init.Mode = DMA_CIRCULAR;                            //使用循环模式
    _dma_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;                    //高优先级
    _dma_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;                //不使用FIFO
    _dma_tx.Init.MemBurst = DMA_MBURST_SINGLE;                    //存储器单次突发传输
    _dma_tx.Init.PeriphBurst = DMA_PBURST_SINGLE;                //外设突发单次传输
    _dma_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    HAL_DMA_DeInit(&_dma_tx);                                    //先清除以前的设置
    HAL_DMA_Init(&_dma_tx);                                        //初始化DMA

    HAL_DMAEx_MultiBufferStart(&_dma_tx,
                               (uint32_t) _stm32_audio_play.tx_fifo,
                               (uint32_t) &SPI2->DR,
                               (uint32_t) _stm32_audio_play.tx_fifo + I2S_TX_DMA_FIFO_SIZE,
                               I2S_TX_DMA_FIFO_SIZE / 2);///开启双缓冲
    rt_thread_delay(20);
    __HAL_DMA_ENABLE_IT(&_dma_tx, DMA_IT_TC);                                     //开启传输完成中断
    HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 1, 0);    //DMA中断优先级
    HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
}

void audio_config_set(struct rt_audio_configure config) {
    audio_channel_set(config.channels);
    audio_samplebits_set(config.samplebits);
    audio_samplerate_set(config.samplerate);

}

rt_err_t audio_init() {
    audio_tx_dma();
    audio_config_init();
    return RT_EOK;
}

static rt_err_t stm32_player_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps) {
    rt_err_t result = RT_EOK;
    struct stm32_audio *st_audio = (struct stm32_audio *) audio->parent.user_data;

    LOG_D("%s:main_type: %d, sub_type: %d", __FUNCTION__, caps->main_type, caps->sub_type);
    switch (caps->main_type) {
        case AUDIO_TYPE_QUERY: /* query the types of hw_codec device */
        {
            switch (caps->sub_type) {
                case AUDIO_TYPE_QUERY:
                    caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER;
                    break;

                default:
                    result = -RT_ERROR;
                    break;
            }

            break;
        }
        case AUDIO_TYPE_OUTPUT: /* Provide capabilities of OUTPUT unit */
        {
            switch (caps->sub_type) {
                case AUDIO_DSP_PARAM:
                    caps->udata.config.channels = st_audio->replay_config.channels;
                    caps->udata.config.samplebits = st_audio->replay_config.samplebits;
                    caps->udata.config.samplerate = st_audio->replay_config.samplerate;
                    break;

                case AUDIO_DSP_SAMPLERATE:
                    caps->udata.config.samplerate = st_audio->replay_config.samplerate;
                    break;

                case AUDIO_DSP_CHANNELS:
                    caps->udata.config.channels = st_audio->replay_config.channels;
                    break;

                case AUDIO_DSP_SAMPLEBITS:
                    caps->udata.config.samplebits = st_audio->replay_config.samplebits;
                    break;

                default:
                    result = -RT_ERROR;
                    break;
            }

            break;
        }

        case AUDIO_TYPE_MIXER: /* report the Mixer Units */
        {
            switch (caps->sub_type) {
                case AUDIO_MIXER_QUERY:
                    caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_LINE;
                    break;
                case AUDIO_MIXER_VOLUME:
                    caps->udata.value = st_audio->replay_volume;
                    break;
                case AUDIO_MIXER_LINE:
                    break;
                default:
                    result = -RT_ERROR;
                    break;
            }

            break;
        }

        default:
            result = -RT_ERROR;
            break;
    }

    return result;
}

static rt_err_t stm32_player_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps) {
    rt_err_t result = RT_EOK;
    struct stm32_audio *st_audio = (struct stm32_audio *) audio->parent.user_data;
    LOG_D("%s:main_type: %d, sub_type: %d", __FUNCTION__, caps->main_type, caps->sub_type);
    switch (caps->main_type) {
        case AUDIO_TYPE_MIXER: {
            switch (caps->sub_type) {
                case AUDIO_MIXER_MUTE: {
                    break;
                }
                case AUDIO_MIXER_VOLUME: {
                    int volume = caps->udata.value;
                    st_audio->replay_volume = volume;
                    /* set mixer volume */
                    wm8978_HPvol_Set(volume, volume);
                    break;
                }
                default:
                    result = -RT_ERROR;
                    break;
            }

            break;
        }
        case AUDIO_TYPE_OUTPUT: {
            switch (caps->sub_type) {
                case AUDIO_DSP_PARAM: {
                    struct rt_audio_configure config = caps->udata.config;
                    st_audio->replay_config.samplerate = config.samplerate;
                    st_audio->replay_config.samplebits = config.samplebits;
                    st_audio->replay_config.channels = config.channels;
                    audio_config_set(config);
                    break;
                }

                case AUDIO_DSP_SAMPLERATE: {
                    st_audio->replay_config.samplerate = caps->udata.config.samplerate; /*设置采样率*/
                    audio_samplerate_set(caps->udata.config.samplerate);
                    break;
                }

                case AUDIO_DSP_CHANNELS: {
                    st_audio->replay_config.channels = caps->udata.config.channels;
                    audio_channel_set(caps->udata.config.channels);
                    break;
                }

                case AUDIO_DSP_SAMPLEBITS: {
                    st_audio->replay_config.samplebits = caps->udata.config.samplebits;
                    audio_samplebits_set(caps->udata.config.samplebits);
                    break;
                }

                default:
                    result = -RT_ERROR;
                    break;
            }
            break;
        }
        default:
            break;
    }

    return result;
}

static rt_err_t stm32_player_init(struct rt_audio_device *audio) {
    /* initialize wm8988 */
    audio_init();
    wm8978_init(CODEC_I2C_NAME, WM8978_ADDR);
    wm8978_cnf_init();
    wm8978_HPvol_Set(30, 30);              //耳机音量设置
    wm8978_SPKvol_Set(50);                     //喇叭音量设置
    wm8978_ADDA_Cfg(1, 0);             //开启DAC
    wm8978_Input_Cfg(0, 0, 0);//关闭输入通道
    wm8978_Output_Cfg(1, 0);         //开启DAC输出
    return RT_EOK;
}

static rt_err_t stm32_player_start(struct rt_audio_device *audio, int stream) {
    if (stream == AUDIO_STREAM_REPLAY) {
        __HAL_DMA_ENABLE(&_dma_tx);
    }
    return RT_EOK;
}

static rt_err_t stm32_player_stop(struct rt_audio_device *audio, int stream) {
    if (stream == AUDIO_STREAM_REPLAY) {
        __HAL_DMA_DISABLE(&_dma_tx);
        /* Clear all flags */
        __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_TC_FLAG_INDEX(&_dma_tx));
        __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_HT_FLAG_INDEX(&_dma_tx));
        __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_TE_FLAG_INDEX(&_dma_tx));
        __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_DME_FLAG_INDEX(&_dma_tx));
        __HAL_DMA_CLEAR_FLAG (&_dma_tx, __HAL_DMA_GET_FE_FLAG_INDEX(&_dma_tx));
    }
    return RT_EOK;
}

rt_ssize_t stm32_player_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) {

    return (rt_ssize_t) size;
}

static void stm32_player_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info) {
    /**
     *               TX_FIFO
     * +----------------+----------------+
     * |     block1     |     block2     |
     * +----------------+----------------+
     *  \  block_size  / \  block_size  /
     */
    info->buffer = _stm32_audio_play.tx_fifo;
    info->total_size = I2S_TX_DMA_FIFO_SIZE * 2;
    info->block_size = I2S_TX_DMA_FIFO_SIZE;
    info->block_count = 2;
}

static struct rt_audio_ops _p_audio_ops =
        {
                .getcaps     = stm32_player_getcaps,
                .configure   = stm32_player_configure,
                .init        = stm32_player_init,
                .start       = stm32_player_start,
                .stop        = stm32_player_stop,
                .transmit    = stm32_player_transmit,
                .buffer_info = stm32_player_buffer_info,
        };

int rt_hw_audio_init(void) {
    rt_uint8_t *tx_fifo;
    tx_fifo = rt_malloc(I2S_TX_DMA_FIFO_SIZE * 2);
    if (tx_fifo == RT_NULL) {
        return -RT_ENOMEM;
    }
    rt_memset(tx_fifo, 0, I2S_TX_DMA_FIFO_SIZE);
    _stm32_audio_play.tx_fifo = tx_fifo;
    _stm32_audio_play.audio.ops = &_p_audio_ops;
    rt_audio_register(&_stm32_audio_play.audio, I2S_SOUND_NAME, RT_DEVICE_FLAG_WRONLY, &_stm32_audio_play);
    return RT_EOK;
}

INIT_DEVICE_EXPORT(rt_hw_audio_init);
void DMA1_Stream4_IRQHandler(void) {
    rt_interrupt_enter();
    if (__HAL_DMA_GET_FLAG(&_dma_tx, DMA_FLAG_TCIF0_4) != RESET) //DMA传输完成
    {
        __HAL_DMA_CLEAR_FLAG(&_dma_tx, DMA_FLAG_TCIF0_4); //清除DMA传输完成中断标志位
        rt_audio_tx_complete(&_stm32_audio_play.audio); /*通知发送下一帧数据*/
    }
    rt_interrupt_leave();
}


IO口初始化

//I2S底层驱动,时钟使能,引脚配置
//此函数会被HAL_I2S_Init()调用
//hi2s:I2S句柄
void HAL_I2S_MspInit(I2S_HandleTypeDef *hi2s) {
    if (hi2s->Instance == SPI2) {
        GPIO_InitTypeDef GPIO_InitStruct;
        /* Peripheral clock enable */
        __HAL_RCC_SPI2_CLK_ENABLE();
        __HAL_RCC_GPIOC_CLK_ENABLE();
        __HAL_RCC_GPIOB_CLK_ENABLE();
        /**I2S2 GPIO Configuration
        PC2     ------> I2S2_ext_SD
        PC3     ------> I2S2_SD
        PB12     ------> I2S2_WS
        PB13     ------> I2S2_CK
        PC6     ------> I2S2_MCK
        */
        GPIO_InitStruct.Pin = GPIO_PIN_2;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF6_I2S2ext;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
        GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_6;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
        GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
}

I2S测试

测试文件(参考官方)

#include <rtthread.h>
#include <rtdevice.h>
#include <dfs_posix.h>

#define BUFSZ   1024
#define SOUND_DEVICE_NAME    "sound0"    /* Audio 设备名称 */
static rt_device_t snd_dev;              /* Audio 设备句柄 */

struct RIFF_HEADER_DEF
{
    char riff_id[4];     // 'R','I','F','F'
    uint32_t riff_size;
    char riff_format[4]; // 'W','A','V','E'
};

struct WAVE_FORMAT_DEF
{
    uint16_t FormatTag;
    uint16_t Channels;
    uint32_t SamplesPerSec;
    uint32_t AvgBytesPerSec;
    uint16_t BlockAlign;
    uint16_t BitsPerSample;
};

struct FMT_BLOCK_DEF
{
    char fmt_id[4];    // 'f','m','t',' '
    uint32_t fmt_size;
    struct WAVE_FORMAT_DEF wav_format;
};

struct DATA_BLOCK_DEF
{
    char data_id[4];     // 'R','I','F','F'
    uint32_t data_size;
};

struct wav_info
{
    struct RIFF_HEADER_DEF header;
    struct FMT_BLOCK_DEF   fmt_block;
    struct DATA_BLOCK_DEF  data_block;
};

int wavplay_sample(int argc, char **argv)
{
    int fd = -1;
    uint8_t *buffer = NULL;
    struct wav_info *info = NULL;
    struct rt_audio_caps caps = {0};
    if (argc != 2)
    {
        rt_kprintf("Usage:\n");
        rt_kprintf("wavplay_sample song.wav\n");
        return 0;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd < 0)
    {
        rt_kprintf("open file failed!\n");
        goto __exit;
    }
    buffer = rt_malloc(BUFSZ);
    if (buffer == RT_NULL)
        goto __exit;
    info = (struct wav_info *) rt_malloc(sizeof * info);
    if (info == RT_NULL)
        goto __exit;
    if (read(fd, &(info->header), sizeof(struct RIFF_HEADER_DEF)) <= 0)
        goto __exit;
    if (read(fd, &(info->fmt_block),  sizeof(struct FMT_BLOCK_DEF)) <= 0)
        goto __exit;
    if (read(fd, &(info->data_block), sizeof(struct DATA_BLOCK_DEF)) <= 0)
        goto __exit;
    rt_kprintf("wav information:\n");
    rt_kprintf("samplerate %d\n", info->fmt_block.wav_format.SamplesPerSec);
    rt_kprintf("channel %d\n", info->fmt_block.wav_format.Channels);
    /* 根据设备名称查找 Audio 设备,获取设备句柄 */
    snd_dev = rt_device_find(SOUND_DEVICE_NAME);
    /* 以只写方式打开 Audio 播放设备 */
    rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY);
    /* 设置采样率、通道、采样位数等音频参数信息 */
    caps.main_type               = AUDIO_TYPE_OUTPUT;                           /* 输出类型(播放设备 )*/
    caps.sub_type                = AUDIO_DSP_PARAM;                             /* 设置所有音频参数信息 */
    caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec;    /* 采样率 */
    caps.udata.config.channels   = info->fmt_block.wav_format.Channels;         /* 采样通道 */
    caps.udata.config.samplebits = 16;                                          /* 采样位数 */
    rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps);

    while (1)
    {
        int length;

        /* 从文件系统读取 wav 文件的音频数据 */
        length = read(fd, buffer, BUFSZ);

        if (length <= 0)
            break;

        /* 向 Audio 设备写入音频数据 */
        rt_device_write(snd_dev, 0, buffer, length);
    }

    /* 关闭 Audio 设备 */
    rt_device_close(snd_dev);

    __exit:

    if (fd >= 0)
        close(fd);

    if (buffer)
        rt_free(buffer);

    if (info)
        rt_free(info);

    return 0;
}

//MSH_CMD_EXPORT(wavplay_sample,  play wav file);

int main() {
    rt_thread_mdelay(1000); // 防止sd卡还未自动挂载
    char *par[2] = {
            "wavplay_sample",
            "/music/whans-maidt.wav"
    };
    wavplay_sample(2, par);
    while (1) {

        rt_thread_delay(1000);
    }
}

结尾

  1. 在stm32f407zgt6板子上测试没有问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/450256.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基础数据运营 - 面经 - 自如租房

招聘要求&#xff1a; 投递时间&#xff1a; 2023.12.28 BOSS直聘 联系HR 2023.12.29 面试 面试流程&#xff1a; 自我介绍&#xff0c;正常完整表述 你能介绍一下你的实习经历吗&#xff1f;主要做了哪些工作&#xff0c;得到了哪些结论出来 一般Excel有用到过么&#x…

Verilog刷题笔记37

题目&#xff1a;3位二进制加法器 Now that you know how to build a full adder, make 3 instances of it to create a 3-bit binary ripple-carry adder. The adder adds two 3-bit numbers and a carry-in to produce a 3-bit sum and carry out. To encourage you to actua…

2014-2023年42家上市银行ESG评级数据集

数据说明&#xff1a;以 Environment&#xff08;环境&#xff09;、Social&#xff08;社会&#xff09;和 Governance&#xff08;公司治理&#xff09;为核心的 ESG 理念是一种关注企业环境、社会责任、治理绩效而非财务绩效的评价标准。2004 年 6 月&#xff0c;联合国全球…

如何下载网页中的网络视频

你应该知道的&#xff0c;如果是要下载网络地图&#xff0c;你可以通过水经微图&#xff08;下文简称“微图”&#xff09;桌面版进行下载。 那么&#xff0c;如果是需要下载网络视频又应该用什么工具呢&#xff1f; 我们这里就来分享一个网络视频下载工具&#xff0c;以及下…

软件无线电系列——软件无线电的发展历程及体系框架

本节目录 一、软件无线电的起始 二、软件无线电SDR论坛 三、SPEAKeasy计划 四、JTRS与SCA 五、软件无线电体系框架本节内容 一、软件无线电的起始 1992年5月&#xff0c;美国电信会议上&#xff0c;Joseph Mitola III博士提出来软件无线电(Software Radio,SR)的概念。理想化的…

探索 PostgreSQL 的高级数据类型 - 第 二 部分

范围类型 范围类型提供了一种简洁的方式来表示单个数据库字段中的值范围。它们在从时间数据到数字间隔的各种领域中都有应用。在本篇博客文章中&#xff0c;我们将通过 DML/SQL 语句和 Navicat for PostgreSQL 16 来深入了解它们的使用&#xff08;以及好处&#xff01;&#…

Ping工作原理

文章目录 目的ping网络协议 OSIICMP什么是ICMP作用功能报文类型查询报文类型差错报文类型ICMP 在 IPv4 和 IPv6 的封装ICMP 在 IPv4 协议中的封装ICMP 在 IPv6 协议中的封装ICMP 头部日常ping 排除步骤ping 查询报文使用code扩展目的 本文主要是梳理ping的工作原理- 揭开 ICMP…

ThreeWayBranch 优化阅读笔记

1. 优化目的 通过重排三分支的 BB 块减少比较指令的执行次数 代码路径: bolt/lib/Passes/ThreeWayBranch.cpp2. 效果 优化前&#xff1a; 注&#xff1a; 黄色数字表示BB块编号&#xff0c; 紫色表示该分支跳转的次数&#xff0c;绿色是代码里BB块的变量名 ThreeWayBranc…

如何把视频号里的视频提取出来?下载提取器支持一键下载视频!

下载微信视频号视频一直都是很多人都痛&#xff0c;本期分享如何使用视频号下载器下载视频。 视频下载提取器 工具名称&#xff1a;视频号下载小助手&#xff1b;简称&#xff0c;视频下载助手 使用平台&#xff1a;苹果手机、安卓手机、电脑设备 使用方法&#xff1a;一键…

零基础学习JS--基础篇--JavaScript类型化数组

JavaScript 类型化数组是一种类似数组的对象&#xff0c;并提供了一种用于在内存缓冲中访问原始二进制数据的机制。 引入类型化数组并非是为了取代 JavaScript 中数组的任何一种功能。相反&#xff0c;它为开发者提供了一个操作二进制数据的接口。这在操作与平台相关的特性时会…

【性能测试】Jmeter+InfluxDB+Grafana 搭建性能监控平台

一、背景 为什么要搭建性能监控平台&#xff1f; 在用 Jmeter 获取性能测试结果的时候&#xff0c;Jmeter自带的测试报告如下&#xff1a; 这个报告有几个很明显的缺点&#xff1a; 只能自己看&#xff0c;无法实时共享&#xff1b;报告信息的展示比较简陋单一&#xff0c;不…

数据库管理-第159期 Oracle Vector DB AI-10(20240311)

数据库管理159期 2024-03-11 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#xff09;1 其他distance函数2 实例演示使用其他函数寻找最近向量点函数变体简写语法 总结 数据库管理-第159期 Oracle Vector DB & AI-10&#xff08;20240311&#x…

如何理解JavaScript一切皆对象

"JavaScript一切皆对象"的说法&#xff0c;实际上是JavaScript编程语言的一个核心特性和设计理念。在JavaScript中&#xff0c;几乎所有的东西都可以被看作是一个对象&#xff0c;包括基本数据类型&#xff08;如数字、字符串、布尔值&#xff09;和复杂的数据结构&a…

算法耗时通用优化技巧 总结

最近在部署AI相关的算法&#xff0c;并要求减少总耗时&#xff0c;从中总结出的一些比较通用的优化技巧。精髓总结一句话就是&#xff1a;在同一时间尽可能充分利用硬件资源。而怎么尽可能充分利用呢&#xff0c;方式就是多线程并行处理。 1、单线程串行处理数据 假设算法需要…

Linux入门基本指令(1)

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2024年3月12日 &#x1f4e8; 博主码云地址&#xff1a;渣渣C &#x1f4d5;参考书籍&#xff1a;…

Java开发从入门到精通(一):Java的项目案例

Java大数据开发和安全开发 Java 案例练习案例一:买飞机票案例二:开发验证码案例三:评委打分案例四:数字加密案例五:数组拷贝案例六: 抢红包案例七:找素数案例八:模拟双色球[拓展案例] Java 案例练习 案例一:买飞机票 分析: 方法是需要接收数据?需要接收机票原价、当前月份、舱…

【更新】cyの破三之旅(20240313~20240421)

序言 在过去一个多月里&#xff0c;我又如期搞砸一些事。 有些事不去做会遗憾&#xff0c;做完也很自责。 经年如此&#xff0c;已经习惯了。 但是这次破三&#xff0c;我一定不会再搞砸了。&#xff08;FLAG已立&#xff0c;完篇收FLAG&#xff09; PS&#xff1a;真的勇…

opengl 学习(三)-----着色器

着色器 分类demo效果解析教程 分类 OPengl C demo #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>#include <string> #include <fstream> #include <sstream>…

【Python数据结构与判断3/7】储存多个数据的元组

目录 导入 数据结构 元组 索引 in运算 ​编辑 Debug 总结 导入 我们之前学习的变量可以存储1个数据&#xff0c;那当我们需要存储10个数据的时候该怎么办呢&#xff1f;写10个变量可以解决这个问题&#xff0c;但是太冗长。 数据结构 我们也可以如第2张图所示&#xff0…

Accelerating Performance of GPU-based Workloads Using CXL——论文泛读

FlexScience 2023 Paper CXL论文阅读笔记整理 问题 跨多GPU系统运行的高性能计算&#xff08;HPC&#xff09;工作负载&#xff0c;如科学模拟和深度学习&#xff0c;是内存和数据密集型的&#xff0c;依赖于主机内存来补充其有限的板载高带宽内存&#xff08;HBM&#xff09…