前言
- rt-thread本次使用的是标志版本为5.0。
- 本次实现的是音频播放部分的i2s驱动
- 使用的芯片为stm32f407zgt6
- 在网上搜了一下关于rt-thread 音频部分的内容,没有实质的提供i2s对接的硬件驱动。所以才有了此次的记录,做一下分享记录。
- 参考的git源码驱动,这个是用的sai接口,而f4并没有
- 本次注重实际代码部分,关于一些音频相关的理论知识自行查阅相关资料。
- 使用到的音频模块为wm8978,相关驱动参考之前的文章
- 音频文件保存到sd中,使用的是rt-thread 的虚拟文件系统组件
rt-thread的audio框架
配置阶段
播放阶段
底层驱动需要做的是,通知框架发送下一帧数据 ,不然会阻塞
录音阶段(本文暂未涉及)
- 过程跟播放是类似的
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);
}
}
结尾
- 在stm32f407zgt6板子上测试没有问题